Bug 35979: (follow-up) Add check in ->enqueue
[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 => 100;
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 our $send_or_die_count = 0;
34
35 my $email_sender_module = Test::MockModule->new('Email::Stuffer');
36 $email_sender_module->mock(
37     'send_or_die',
38     sub {
39         ( $email_object, $sendmail_params ) = @_;
40         my $str = $email_object->email->as_string;
41         unlike $str, qr/I =C3=A2=C2=99=C2=A5 Koha=/, "Content is not double encoded";
42         $send_or_die_count++;
43         warn "Fake send_or_die";
44     }
45 );
46
47 use_ok('C4::Context');
48 use_ok('C4::Members');
49 use_ok('C4::Acquisition', qw( NewBasket ));
50 use_ok('C4::Biblio', qw( AddBiblio GetBiblioData ));
51 use_ok('C4::Letters', qw( GetMessageTransportTypes GetMessage EnqueueLetter GetQueuedMessages SendQueuedMessages ResendMessage GetLetters GetPreparedLetter SendAlerts ));
52 use t::lib::Mocks;
53 use t::lib::TestBuilder;
54 use Koha::Database;
55 use Koha::DateUtils qw( dt_from_string output_pref );
56 use Koha::Acquisition::Booksellers;
57 use Koha::Acquisition::Bookseller::Contacts;
58 use Koha::Acquisition::Orders;
59 use Koha::Libraries;
60 use Koha::Notice::Messages;
61 use Koha::Notice::Templates;
62 use Koha::Notice::Util;
63 use Koha::Patrons;
64 use Koha::Subscriptions;
65
66 my $schema = Koha::Database->schema;
67 $schema->storage->txn_begin();
68
69 my $builder = t::lib::TestBuilder->new;
70 my $dbh = C4::Context->dbh;
71
72 $dbh->do(q|DELETE FROM letter|);
73 $dbh->do(q|DELETE FROM message_queue|);
74 $dbh->do(q|DELETE FROM message_transport_types|);
75
76 my $library = $builder->build({
77     source => 'Branch',
78     value  => {
79         branchemail      => 'branchemail@address.com',
80         branchreplyto    => 'branchreplyto@address.com',
81         branchreturnpath => 'branchreturnpath@address.com',
82     }
83 });
84 my $patron_category = $builder->build({ source => 'Category' })->{categorycode};
85 my $date = dt_from_string;
86 my $borrowernumber = Koha::Patron->new({
87     firstname    => 'Jane',
88     surname      => 'Smith',
89     categorycode => $patron_category,
90     branchcode   => $library->{branchcode},
91     dateofbirth  => $date,
92     smsalertnumber => undef,
93 })->store->borrowernumber;
94
95 my $marc_record = MARC::Record->new;
96 my( $biblionumber, $biblioitemnumber ) = AddBiblio( $marc_record, '' );
97
98
99
100 # GetMessageTransportTypes
101 my $mtts = C4::Letters::GetMessageTransportTypes();
102 is( @$mtts, 0, 'GetMessageTransportTypes returns the correct number of message types' );
103
104 $dbh->do(q|
105     INSERT INTO message_transport_types( message_transport_type ) VALUES ('email'), ('phone'), ('print'), ('sms')
106 |);
107 $mtts = C4::Letters::GetMessageTransportTypes();
108 is_deeply( $mtts, ['email', 'phone', 'print', 'sms'], 'GetMessageTransportTypes returns all values' );
109
110
111 # EnqueueLetter
112 is( C4::Letters::EnqueueLetter(), undef, 'EnqueueLetter without argument returns undef' );
113
114 my $my_message = {
115     borrowernumber         => $borrowernumber,
116     message_transport_type => 'sms',
117     to_address             => undef,
118     from_address           => 'from@example.com',
119 };
120 my $message_id = C4::Letters::EnqueueLetter($my_message);
121 is( $message_id, undef, 'EnqueueLetter without the letter argument returns undef' );
122
123 delete $my_message->{message_transport_type};
124 $my_message->{letter} = {
125     content      => 'I ♥ Koha',
126     title        => '啤酒 is great',
127     metadata     => 'metadata',
128     code         => 'TEST_MESSAGE',
129     content_type => 'text/plain',
130 };
131
132 $message_id = C4::Letters::EnqueueLetter($my_message);
133 is( $message_id, undef, 'EnqueueLetter without the message type argument argument returns undef' );
134
135 $my_message->{message_transport_type} = 'sms';
136 $message_id = C4::Letters::EnqueueLetter($my_message);
137 ok(defined $message_id && $message_id > 0, 'new message successfully queued');
138
139
140 # GetQueuedMessages
141 my $messages = C4::Letters::GetQueuedMessages();
142 is( @$messages, 1, 'GetQueuedMessages without argument returns all the entries' );
143
144 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
145 is( @$messages, 1, 'one message stored for the borrower' );
146 is( $messages->[0]->{message_id}, $message_id, 'EnqueueLetter returns the message id correctly' );
147 is( $messages->[0]->{borrowernumber}, $borrowernumber, 'EnqueueLetter stores the borrower number correctly' );
148 is( $messages->[0]->{subject}, $my_message->{letter}->{title}, 'EnqueueLetter stores the subject correctly' );
149 is( $messages->[0]->{content}, $my_message->{letter}->{content}, 'EnqueueLetter stores the content correctly' );
150 is( $messages->[0]->{message_transport_type}, $my_message->{message_transport_type}, 'EnqueueLetter stores the message type correctly' );
151 is( $messages->[0]->{status}, 'pending', 'EnqueueLetter stores the status pending correctly' );
152 isnt( $messages->[0]->{time_queued}, undef, 'Time queued inserted by default in message_queue table' );
153 is( $messages->[0]->{updated_on}, $messages->[0]->{time_queued}, 'Time status changed equals time queued when created in message_queue table' );
154 is( $messages->[0]->{failure_code}, '', 'Failure code for successful message correctly empty');
155
156 # Setting time_queued to something else than now
157 my $yesterday = dt_from_string->subtract( days => 1 );
158 Koha::Notice::Messages->find($messages->[0]->{message_id})->time_queued($yesterday)->store;
159
160 # SendQueuedMessages
161
162 throws_ok {
163     C4::Letters::SendQueuedMessages( { message_id => undef } );
164 }
165 'Koha::Exceptions::BadParameter', 'Undef message_id throws an exception';
166
167 throws_ok {
168     C4::Letters::SendQueuedMessages( { message_id => 0 } );
169 }
170 'Koha::Exceptions::BadParameter', 'message_id of 0 throws an exception';
171
172 throws_ok {
173     C4::Letters::SendQueuedMessages( { message_id => q{} } );
174 }
175 'Koha::Exceptions::BadParameter', 'Empty string message_id throws an exception';
176
177 my $messages_processed = C4::Letters::SendQueuedMessages( { type => 'email' });
178 is($messages_processed, 0, 'No queued messages processed if type limit passed with unused type');
179 $messages_processed = C4::Letters::SendQueuedMessages( { type => 'sms' });
180 is($messages_processed, 0, 'All queued messages processed, nothing sent');
181 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
182 is(
183     $messages->[0]->{status},
184     'failed',
185     'message marked failed if tried to send SMS message for borrower with no smsalertnumber set (bug 11208)'
186 );
187 is(
188     $messages->[0]->{failure_code},
189     'MISSING_SMS',
190     'Correct failure code set for borrower with no smsalertnumber set'
191 );
192 isnt($messages->[0]->{updated_on}, $messages->[0]->{time_queued}, 'Time status changed differs from time queued when status changes' );
193 is(dt_from_string($messages->[0]->{time_queued}), $yesterday, 'Time queued remaines inmutable' );
194
195 # ResendMessage
196 my $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
197 my $message = C4::Letters::GetMessage( $messages->[0]->{message_id});
198 is( $resent, 1, 'The message should have been resent' );
199 is($message->{status},'pending', 'ResendMessage sets status to pending correctly (bug 12426)');
200 $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
201 is( $resent, 0, 'The message should not have been resent again' );
202 $resent = C4::Letters::ResendMessage();
203 is( $resent, undef, 'ResendMessage should return undef if not message_id given' );
204
205 # Delivery notes
206 is( $messages->[0]->{failure_code}, 'MISSING_SMS', 'Failure code set correctly for no smsalertnumber correctly set' );
207
208 # GetLetters
209 my $letters = C4::Letters::GetLetters();
210 is( @$letters, 0, 'GetLetters returns the correct number of letters' );
211
212 my $title = q|<<branches.branchname>> - <<status>>|;
213 my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>,
214 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.
215
216 <<branches.branchname>>
217 <<branches.branchaddress1>>
218 URL: <<OPACBaseURL>>
219
220 The following item(s) is/are currently <<status>>:
221
222 <item> <<count>>. <<items.itemcallnumber>>, Barcode: <<items.barcode>> </item>
223
224 Thank-you for your prompt attention to this matter.
225 Don't forget your date of birth: <<borrowers.dateofbirth>>.
226 Look at this wonderful biblio timestamp: <<biblio.timestamp>>.
227 };
228
229 $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 );
230 $letters = C4::Letters::GetLetters();
231 is( @$letters, 1, 'GetLetters returns the correct number of letters' );
232 is( $letters->[0]->{module}, 'my module', 'GetLetters gets the module correctly' );
233 is( $letters->[0]->{code}, 'my code', 'GetLetters gets the code correctly' );
234 is( $letters->[0]->{name}, 'my name', 'GetLetters gets the name correctly' );
235
236 my $data_1 = { module => 'blah', code => 'ISBN', name => 'book' };
237 my $data_2 = { module => 'blah', code => 'ISSN' };
238 $builder->build_object( { class => 'Koha::Notice::Templates', value => $data_1 } );
239 $builder->build_object( { class => 'Koha::Notice::Templates', value => $data_2 } );
240
241 $letters = GetLetters( { module => 'blah' } );
242 is( scalar(@$letters), 2, 'GetLetters returns the 2 inserted letters' );
243
244 my ($ISBN_letter) = grep { $_->{code} eq 'ISBN' } @$letters;
245 is( $ISBN_letter->{name}, 'book', 'letter name for "ISBN" letter is book' );
246
247 # GetPreparedLetter
248 t::lib::Mocks::mock_preference('OPACBaseURL', 'http://thisisatest.com');
249 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
250 t::lib::Mocks::mock_preference( 'ReplytoDefault', q{} );
251
252 my $sms_content = 'This is a SMS for an <<status>>';
253 $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 );
254
255 my $tables = {
256     borrowers => $borrowernumber,
257     branches => $library->{branchcode},
258     biblio => $biblionumber,
259 };
260 my $substitute = {
261     status => 'overdue',
262 };
263 my $repeat = [
264     {
265         itemcallnumber => 'my callnumber1',
266         barcode        => '1234',
267     },
268     {
269         itemcallnumber => 'my callnumber2',
270         barcode        => '5678',
271     },
272 ];
273 my $prepared_letter = GetPreparedLetter((
274     module      => 'my module',
275     branchcode  => $library->{branchcode},
276     letter_code => 'my code',
277     tables      => $tables,
278     substitute  => $substitute,
279     repeat      => $repeat,
280 ));
281 my $retrieved_library = Koha::Libraries->find($library->{branchcode});
282 my $my_title_letter = $retrieved_library->branchname . qq| - $substitute->{status}|;
283 my $biblio_timestamp = dt_from_string( GetBiblioData($biblionumber)->{timestamp} );
284 my $my_content_letter = qq|Dear Jane Smith,
285 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.
286
287 |.$retrieved_library->branchname.qq|
288 |.$retrieved_library->branchaddress1.qq|
289 URL: http://thisisatest.com
290
291 The following item(s) is/are currently $substitute->{status}:
292
293 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
294 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
295
296 Thank-you for your prompt attention to this matter.
297 Don't forget your date of birth: | . output_pref({ dt => $date, dateonly => 1 }) . q|.
298 Look at this wonderful biblio timestamp: | . output_pref({ dt => $biblio_timestamp })  . ".\n";
299
300 is( $prepared_letter->{title}, $my_title_letter, 'GetPreparedLetter returns the title correctly' );
301 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
302
303 $prepared_letter = GetPreparedLetter((
304     module                 => 'my module',
305     branchcode             => $library->{branchcode},
306     letter_code            => 'my code',
307     tables                 => $tables,
308     substitute             => $substitute,
309     repeat                 => $repeat,
310     message_transport_type => 'sms',
311 ));
312 $my_content_letter = qq|This is a SMS for an $substitute->{status}|;
313 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
314
315 warning_is {
316     $prepared_letter = GetPreparedLetter((
317         module                 => 'my module',
318         branchcode             => $library->{branchcode},
319         letter_code            => 'my code',
320         tables                 => $tables,
321         substitute             => { status => undef },
322         repeat                 => $repeat,
323         message_transport_type => 'sms',
324     ));
325 }
326 undef, "No warning if GetPreparedLetter called with substitute containing undefined value";
327 is( $prepared_letter->{content}, q|This is a SMS for an |, 'GetPreparedLetter returns the content correctly when substitute contains undefined value' );
328
329 $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>>.');});
330 $prepared_letter = GetPreparedLetter((
331     module                 => 'test_date',
332     branchcode             => '',
333     letter_code            => 'test_date',
334     tables                 => $tables,
335     substitute             => $substitute,
336     repeat                 => $repeat,
337 ));
338 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
339
340 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp | dateonly>>.' WHERE code = 'test_date';});
341 $prepared_letter = GetPreparedLetter((
342     module                 => 'test_date',
343     branchcode             => '',
344     letter_code            => 'test_date',
345     tables                 => $tables,
346     substitute             => $substitute,
347     repeat                 => $repeat,
348 ));
349 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
350
351 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp|dateonly >>.' WHERE code = 'test_date';});
352 $prepared_letter = GetPreparedLetter((
353     module                 => 'test_date',
354     branchcode             => '',
355     letter_code            => 'test_date',
356     tables                 => $tables,
357     substitute             => $substitute,
358     repeat                 => $repeat,
359 ));
360 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
361
362 t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
363 my $yesterday_night = $date->clone->add( days => -1 )->set_hour(22);
364 $dbh->do(q|UPDATE biblio SET timestamp = ? WHERE biblionumber = ?|, undef, $yesterday_night, $biblionumber );
365 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp>>.' WHERE code = 'test_date';});
366 $prepared_letter = GetPreparedLetter((
367     module                 => 'test_date',
368     branchcode             => '',
369     letter_code            => 'test_date',
370     tables                 => $tables,
371     substitute             => $substitute,
372     repeat                 => $repeat,
373 ));
374 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
375
376 $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>');});
377 $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> Basket name: [% basket.basketname %]');});
378
379 my $testacqorder2_content = <<EOF;
380 [%- USE Price -%]
381 [% bookseller.name %]
382 [% FOREACH order IN orders %]
383 Ordernumber [% order.ordernumber %] [% order.quantity %] [% order.listprice | \$Price %]
384 [% END %]
385 EOF
386
387 $dbh->do("INSERT INTO letter (module, code, name, title, content) VALUES ('orderacquisition','TESTACQORDER2','Acquisition Order','Order','$testacqorder2_content');");
388
389 my $popito = $builder->build({
390     source => 'Aqbookseller',
391     value  => { name => 'Popito' }
392 });
393
394 my $order_1 = $builder->build({
395     source => 'Aqorder',
396     value  => {
397        quantity => 2,
398        listprice => '12.00'
399     }
400 });
401
402 my $order_2 = $builder->build({
403     source => 'Aqorder',
404     value  => {
405        quantity => 1,
406        listprice => '23.50'
407     }
408 });
409
410 $prepared_letter = GetPreparedLetter((
411     module                 => 'orderacquisition',
412     branchcode             => '',
413     letter_code            => 'TESTACQORDER2',
414     tables                 => { 'aqbooksellers' => $popito->{id} },
415     loops                  => {
416         aqorders => [ $order_1->{ordernumber}, $order_2->{ordernumber} ]
417     }
418 ));
419
420 my $testacqorder2_expected = qq|Popito
421
422 Ordernumber | . $order_1->{ordernumber} . qq| 2 12.00
423
424 Ordernumber | . $order_2->{ordernumber} . qq| 1 23.50
425
426 |;
427
428 is($prepared_letter->{content}, $testacqorder2_expected);
429
430 # Test that _parseletter doesn't modify its parameters bug 15429
431 {
432     my $values = { dateexpiry => '2015-12-13', };
433     C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
434     is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
435 }
436
437 # Correctly format dateexpiry
438 {
439     my $values = { dateexpiry => '2015-12-13', };
440
441     t::lib::Mocks::mock_preference('dateformat', 'metric');
442     t::lib::Mocks::mock_preference('timeformat', '24hr');
443     my $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
444     is( $letter->{content}, 'expiry on 13/12/2015' );
445
446     t::lib::Mocks::mock_preference('dateformat', 'metric');
447     t::lib::Mocks::mock_preference('timeformat', '12hr');
448     $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
449     is( $letter->{content}, 'expiry on 13/12/2015' );
450 }
451
452 my $bookseller = Koha::Acquisition::Bookseller->new(
453     {
454         name => "my vendor",
455         address1 => "bookseller's address",
456         phone => "0123456",
457         active => 1,
458         deliverytime => 5,
459     }
460 )->store;
461 my $booksellerid = $bookseller->id;
462
463 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith',  phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
464 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues      => 1, booksellerid => $booksellerid } )->store;
465 my $basketno = NewBasket($booksellerid, 1, 'The basket name');
466
467 my $budgetid = C4::Budgets::AddBudget({
468     budget_code => "budget_code_test_letters",
469     budget_name => "budget_name_test_letters",
470 });
471
472 my $bib = MARC::Record->new();
473 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
474     $bib->append_fields(
475         MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
476     );
477 } else {
478     $bib->append_fields(
479         MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
480     );
481 }
482
483 my $logged_in_user = $builder->build_object(
484     {
485         class => 'Koha::Patrons',
486         value => {
487             branchcode => $library->{branchcode},
488             email      => 'some@email.com'
489         }
490     }
491 );
492
493 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
494
495 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
496 my $order = Koha::Acquisition::Order->new(
497     {
498         basketno => $basketno,
499         quantity => 1,
500         biblionumber => $biblionumber,
501         budget_id => $budgetid,
502     }
503 )->store;
504 my $ordernumber = $order->ordernumber;
505
506 Koha::Acquisition::Baskets->find( $basketno )->close;
507 my $err;
508 warning_like {
509     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
510     qr/^Bookseller .* without emails at/,
511     "SendAlerts prints a warning";
512 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
513
514 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
515 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
516
517 # Ensure that the preference 'ClaimsLog' is set to logging
518 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
519
520 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
521 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
522
523 {
524     warning_like {
525         $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' )
526     }
527     qr|Fake send_or_die|,
528         "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
529     is( $err,                                            1,                        "Successfully sent order." );
530     is( $email_object->email->header('To'),              'testemail@mydomain.com', "mailto correct in sent order" );
531     is( scalar $email_object->email->header('Reply-To'), undef,                    "No reply-to address is set" );
532     is(
533         $email_object->email->body,
534         'my vendor|John Smith|Ordernumber '
535             . $ordernumber
536             . ' (Silence in the library) (1 ordered) Basket name: The basket name',
537         'Order notice text constructed successfully'
538     );
539
540     # SendAlerts should use specific email addresses if set
541     t::lib::Mocks::mock_preference( 'AcquisitionsDefaultEmailAddress', 'acq-default@domain.com' );
542     t::lib::Mocks::mock_preference( 'AcquisitionsDefaultReplyTo',      'acq-replyto@domain.com' );
543
544     warning_like {
545         $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' )
546     }
547     qr|Fake send_or_die|,
548         "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
549     is(
550         $email_object->email->header('From'), 'acq-default@domain.com',
551         "AcquisitionsDefaultEmailAddress is used to sent acq notification"
552     );
553     is(
554         $email_object->email->header('Reply-To'), 'acq-replyto@domain.com',
555         "AcquisitionsDefaultReplyTo is used to sent acq notification"
556     );
557
558     my $mocked_koha_email = Test::MockModule->new('Koha::Email');
559     $mocked_koha_email->mock(
560         'send_or_die',
561         sub {
562             Email::Sender::Failure->throw('something went wrong');
563         }
564     );
565
566     warning_like {
567         $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' );
568     }
569     qr{something went wrong},
570         'Warning is printed';
571
572     is( $err->{error}, 'something went wrong', "Send exception, error message returned" );
573
574     $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
575     warning_like {
576         $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' )
577     }
578     qr/No orderacquisition TESTACQORDER letter transported by email/,
579         "GetPreparedLetter warns about missing notice template";
580     is( $err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined." );
581 }
582
583 {
584 warning_like {
585     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
586     qr|Fake send_or_die|,
587     "SendAlerts is using the mocked send_or_die routine";
588
589 is($err, 1, "Successfully sent claim");
590 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
591 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
592
593 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
594 $mocked_koha_email->mock( 'send_or_die', sub {
595     Email::Sender::Failure->throw('something went wrong');
596 });
597
598 warning_like {
599     $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
600     qr{something went wrong},
601     'Warning is printed';
602
603 is($err->{error}, 'something went wrong', "Send exception, error message returned");
604 }
605
606 {
607 use C4::Serials qw( NewSubscription GetSerials findSerialsByStatus ModSerialStatus );
608
609 my $notes = 'notes';
610 my $internalnotes = 'intnotes';
611 my $number_pattern = $builder->build_object(
612     {
613         class => 'Koha::Subscription::Numberpatterns',
614         value => { numberingmethod => 'No. {X}' }
615     }
616 );
617 my $subscriptionid = NewSubscription(
618      undef,      "",     undef, undef, undef, $biblionumber,
619     '2013-01-01', 1, undef, undef,  undef,
620     undef,      undef,  undef, undef, undef, undef,
621     1,          $notes,undef, '2013-01-01', undef, $number_pattern->id,
622     undef,       undef,  0,    $internalnotes,  0,
623     undef, undef, 0,          undef,         '2013-12-31', 0
624 );
625 $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>>');});
626 my ($serials_count, @serials) = GetSerials($subscriptionid);
627 my $serial = $serials[0];
628
629 my $patron = Koha::Patron->new({
630     firstname    => 'John',
631     surname      => 'Smith',
632     categorycode => $patron_category,
633     branchcode   => $library->{branchcode},
634     dateofbirth  => $date,
635     email        => 'john.smith@test.de',
636 })->store;
637 my $borrowernumber = $patron->borrowernumber;
638 my $subscription = Koha::Subscriptions->find( $subscriptionid );
639 $subscription->add_subscriber( $patron );
640
641 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
642 my $err2;
643 warning_like {
644 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
645     qr|Fake send_or_die|,
646     "SendAlerts is using the mocked send_or_die routine";
647
648 is($err2, 1, "Successfully sent serial notification");
649 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
650 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
651
652 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
653
654 my $err3;
655 warning_like {
656 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
657     qr|Fake send_or_die|,
658     "SendAlerts is using the mocked send_or_die routine";
659 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
660
661 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
662 $mocked_koha_email->mock( 'send_or_die', sub {
663     Email::Sender::Failure->throw('something went wrong');
664 });
665
666 warning_like {
667     $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
668     qr{something went wrong},
669     'Warning is printed';
670
671 is($err->{error}, 'something went wrong', "Send exception, error message returned");
672
673 }
674 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
675
676 subtest '_parseletter' => sub {
677     plan tests => 2;
678
679     # Regression test for bug 10843
680     # $dt->add takes a scalar, not undef
681     my $letter;
682     t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay', undef );
683     $letter = C4::Letters::_parseletter( undef, 'reserves', { waitingdate => "2013-01-01" } );
684     is( ref($letter), 'HASH' );
685     t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay', 1 );
686     $letter = C4::Letters::_parseletter( undef, 'reserves', { waitingdate => "2013-01-01" } );
687     is( ref($letter), 'HASH' );
688 };
689
690 subtest 'SendAlerts - claimissue' => sub {
691     plan tests => 18;
692
693     use C4::Serials;
694
695     $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>>');});
696
697     my $bookseller = Koha::Acquisition::Bookseller->new(
698         {
699             name => "my vendor",
700             address1 => "bookseller's address",
701             phone => "0123456",
702             active => 1,
703             deliverytime => 5,
704         }
705     )->store;
706     my $booksellerid = $bookseller->id;
707
708     Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
709
710     my $bib = MARC::Record->new();
711     if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
712         $bib->append_fields(
713             MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
714             MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
715         );
716     } else {
717         $bib->append_fields(
718             MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
719             MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
720         );
721     }
722     my ($biblionumber) = AddBiblio($bib, '');
723
724     my $number_pattern = $builder->build_object(
725         {
726             class => 'Koha::Subscription::Numberpatterns',
727             value => { numberingmethod => 'No. {X}' }
728         }
729     );
730
731     my $subscriptionid = NewSubscription(
732          undef, "", $booksellerid, undef, undef, $biblionumber,
733         '2013-01-01', 1, undef, undef,  undef,
734         undef,  undef,  undef, undef, undef, undef,
735         1, 'public',undef, '2013-01-01', undef, $number_pattern->id,
736         undef, undef,  0, 'internal',  0,
737         undef, undef, 0,  undef, '2013-12-31', 0
738     );
739
740     my ($serials_count, @serials) = GetSerials($subscriptionid);
741     my  @serialids = ($serials[0]->{serialid});
742
743     my $err;
744     warning_like {
745         $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
746         qr/^Bookseller .* without emails at/,
747         "Warn on vendor without email address";
748
749     $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
750     $bookseller->contacts->next->email('testemail@mydomain.com')->store;
751
752     # Ensure that the preference 'ClaimsLog' is set to logging
753     t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
754
755     # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
756     t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
757
758     t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
759
760     {
761     warning_like {
762         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
763         qr|Fake send_or_die|,
764         "SendAlerts is using the mocked send_or_die routine (claimissues)";
765     is( $err, 1, "Successfully sent claim" );
766     is( $email_object->email->header('To'),
767         'testemail@mydomain.com', "mailto correct in sent claim" );
768     is( scalar $email_object->email->header('Reply-To'), undef, "reply-to is not set" );
769     is(
770         $email_object->email->body,
771         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
772         'Serial claim letter for 1 issue constructed successfully'
773     );
774     }
775
776     t::lib::Mocks::mock_preference( 'SerialsDefaultEmailAddress', 'ser-default@domain.com' );
777     t::lib::Mocks::mock_preference( 'SerialsDefaultReplyTo', 'ser-replyto@domain.com' );
778
779     {
780     warning_like {
781         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
782         qr|Fake send_or_die|,
783         "SendAlerts is using the mocked send_or_die routine (claimissues)";
784     is( $email_object->email->header('From'),
785         'ser-default@domain.com', "SerialsDefaultEmailAddress is used to serial claim issue" );
786     is( $email_object->email->header('Reply-To'),
787         'ser-replyto@domain.com', "SerialsDefaultReplyTo is used to sent serial claim issue" );
788
789     my $mocked_koha_email = Test::MockModule->new('Koha::Email');
790     $mocked_koha_email->mock( 'send_or_die', sub {
791             Email::Sender::Failure->throw('something went wrong');
792     });
793
794     warning_like {
795         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
796         qr{something went wrong},
797         'Warning is printed';
798
799     is($err->{error}, 'something went wrong', "Send exception, error message returned");
800     }
801
802     {
803     my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
804     my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
805     ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
806     ($serials_count, @serials) = GetSerials($subscriptionid);
807     push @serialids, ($serials[1]->{serialid});
808
809     warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
810         qr|Fake send_or_die|,
811         "SendAlerts is using the mocked send_or_die routine (claimissues)";
812
813     is(
814         $email_object->email->body,
815         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
816           . $email_object->email->crlf
817           . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
818         "Serial claim letter for 2 issues constructed successfully"
819     );
820
821     $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
822     warning_like {
823         $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
824         qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
825         "GetPreparedLetter warns about missing notice template";
826     is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
827     }
828
829 };
830
831 subtest 'GetPreparedLetter' => sub {
832     plan tests => 4;
833
834     Koha::Notice::Template->new(
835         {
836             module                 => 'test',
837             code                   => 'test',
838             branchcode             => '',
839             message_transport_type => 'email'
840         }
841     )->store;
842     my $letter;
843     warning_like {
844         $letter = C4::Letters::GetPreparedLetter(
845             module      => 'test',
846             letter_code => 'test',
847         );
848     }
849     qr{^ERROR: nothing to substitute},
850 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
851     is( $letter, undef,
852 'No letter should be returned by GetPreparedLetter if something went wrong'
853     );
854
855     warning_like {
856         $letter = C4::Letters::GetPreparedLetter(
857             module      => 'test',
858             letter_code => 'test',
859             substitute  => {}
860         );
861     }
862     qr{^ERROR: nothing to substitute},
863 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
864     is( $letter, undef,
865 'No letter should be returned by GetPreparedLetter if something went wrong'
866     );
867
868 };
869
870
871
872 subtest 'TranslateNotices' => sub {
873     plan tests => 4;
874
875     t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
876
877     $dbh->do(
878         q|
879         INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
880         ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
881         ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
882     | );
883     my $substitute = {};
884     my $letter = C4::Letters::GetPreparedLetter(
885             module                 => 'test',
886             tables                 => $tables,
887             letter_code            => 'code',
888             message_transport_type => 'email',
889             substitute             => $substitute,
890     );
891     is(
892         $letter->{title},
893         'a test',
894         'GetPreparedLetter should return the default one if the lang parameter is not provided'
895     );
896
897     $letter = C4::Letters::GetPreparedLetter(
898             module                 => 'test',
899             tables                 => $tables,
900             letter_code            => 'code',
901             message_transport_type => 'email',
902             substitute             => $substitute,
903             lang                   => 'es-ES',
904     );
905     is( $letter->{title}, 'una prueba',
906         'GetPreparedLetter should return the required notice if it exists' );
907
908     $letter = C4::Letters::GetPreparedLetter(
909             module                 => 'test',
910             tables                 => $tables,
911             letter_code            => 'code',
912             message_transport_type => 'email',
913             substitute             => $substitute,
914             lang                   => 'fr-FR',
915     );
916     is(
917         $letter->{title},
918         'a test',
919         'GetPreparedLetter should return the default notice if the one required does not exist'
920     );
921
922     t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
923
924     $letter = C4::Letters::GetPreparedLetter(
925             module                 => 'test',
926             tables                 => $tables,
927             letter_code            => 'code',
928             message_transport_type => 'email',
929             substitute             => $substitute,
930             lang                   => 'es-ES',
931     );
932     is( $letter->{title}, 'a test',
933         'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
934
935 };
936
937 subtest 'Test SMS handling in SendQueuedMessages' => sub {
938
939     plan tests => 14;
940
941     t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
942     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
943
944     my $patron = Koha::Patrons->find($borrowernumber);
945     $dbh->do(q|
946         INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
947         VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
948         |, undef, $borrowernumber
949     );
950     eval { C4::Letters::SendQueuedMessages(); };
951     is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
952
953     my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
954     $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
955     $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
956
957     warning_like { C4::Letters::SendQueuedMessages(); }
958         qr|Fake send_or_die|,
959         "SendAlerts is using the mocked send_or_die routine (claimissues)";
960
961     my $message = $schema->resultset('MessageQueue')->search({
962         borrowernumber => $borrowernumber,
963         status => 'sent'
964     })->next();
965
966     is( $message->letter_id, $messages->[0]->{id}, "Message letter_id is set correctly" );
967     is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
968     is(
969         $message->from_address(),
970         'from@example.com',
971         'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
972     );
973
974     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
975
976     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
977
978     $message_id = C4::Letters::EnqueueLetter($my_message);
979     warning_like { C4::Letters::SendQueuedMessages(); }
980         qr|Fake send_or_die|,
981         "SendAlerts is using the mocked send_or_die routine (claimissues)";
982
983     $message = $schema->resultset('MessageQueue')->search({
984         borrowernumber => $borrowernumber,
985         status => 'sent'
986     })->next();
987
988     is(
989         $message->from_address(),
990         'override@example.com',
991         'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
992     );
993
994     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
995     $my_message->{to_address} = 'fixme@kidclamp.iswrong';
996     $message_id = C4::Letters::EnqueueLetter($my_message);
997
998     my $number_attempted = C4::Letters::SendQueuedMessages({
999         borrowernumber => -1, # -1 still triggers the borrowernumber condition
1000         letter_code    => 'PASSWORD_RESET',
1001     });
1002     is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
1003
1004     warning_like { C4::Letters::SendQueuedMessages(); }
1005         qr|Fake send_or_die|,
1006         "SendAlerts is using the mocked send_or_die routine (claimissues)";
1007
1008     my $sms_message_address = $schema->resultset('MessageQueue')->search({
1009         borrowernumber => $borrowernumber,
1010         status => 'sent'
1011     })->next()->to_address();
1012     is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
1013
1014     # Test using SMS::Send::Test driver that's bundled with SMS::Send
1015     t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
1016
1017     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1018     C4::Letters::EnqueueLetter($my_message);
1019     C4::Letters::SendQueuedMessages();
1020
1021     $sms_message_address = $schema->resultset('MessageQueue')->search({
1022         borrowernumber => $borrowernumber,
1023         status => 'sent'
1024     })->next()->to_address();
1025     is( $sms_message_address, '5555555555', 'SendQueuedMessages populates the to address correctly for SMS by SMS::Send driver to smsalertnumber when to_address is set incorrectly' );
1026
1027     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1028 };
1029
1030 subtest 'Test guarantor handling in SendQueuedMessages' => sub {
1031
1032     plan tests => 19;
1033
1034     t::lib::Mocks::mock_preference( 'borrowerRelationship', 'test' );
1035     t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 1 );
1036
1037     my $patron     = Koha::Patrons->find($borrowernumber);
1038
1039     my $patron_category =
1040         $builder->build( { source => 'Category', value => { category_type => 'A', can_be_guarantee => 0 } } )
1041         ->{categorycode};
1042     my $guarantor1 = $builder->build_object(
1043         { class => 'Koha::Patrons', value => { email => 'g1@email.com', categorycode => $patron_category } } );
1044     my $guarantor2 = $builder->build_object(
1045         { class => 'Koha::Patrons', value => { email => 'g2@email.com', categorycode => $patron_category } } );
1046
1047     $patron->add_guarantor( { guarantor_id => $guarantor1->borrowernumber, relationship => 'test' } );
1048     $patron->add_guarantor( { guarantor_id => $guarantor2->borrowernumber, relationship => 'test' } );
1049
1050     $my_message = {
1051         'letter' => {
1052             'content'      => 'a message',
1053             'metadata'     => 'metadata',
1054             'code'         => 'TEST_MESSAGE',
1055             'content_type' => 'text/plain',
1056             'title'        => 'message title'
1057         },
1058         'borrowernumber'         => $borrowernumber,
1059         'to_address'             => undef,
1060         'message_transport_type' => 'email',
1061         'from_address'           => 'from@example.com'
1062     };
1063     $message_id = C4::Letters::EnqueueLetter($my_message);
1064
1065     # feature disabled
1066     t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '0' );
1067
1068     warning_like { C4::Letters::SendQueuedMessages(); }
1069     qr|No 'to_address', email address or guarantors email address for borrowernumber|,
1070         "SendQueuedMessages fails when no to_address, patron notice email and RedirectGuaranteeEmail is not set";
1071
1072     # feature enabled
1073     t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '1' );
1074
1075     # reset message - testing without to or borrower valid email
1076     Koha::Notice::Messages->find($message_id)->delete;
1077     $message_id = C4::Letters::EnqueueLetter($my_message);
1078
1079     warning_like { C4::Letters::SendQueuedMessages(); }
1080     qr|Fake send_or_die|,
1081         "SendQueuedMessages is using the mocked send_or_die routine";
1082
1083     $message = $schema->resultset('MessageQueue')->search(
1084         {
1085             borrowernumber => $borrowernumber,
1086             status         => 'sent'
1087         }
1088     )->next();
1089
1090     is(
1091         $message->to_address(),
1092         $guarantor1->email,
1093         'SendQueuedMessages uses first guarantor email for "to" when patron has no email'
1094     );
1095
1096     is(
1097         $message->cc_address(),
1098         $guarantor2->email,
1099         'SendQueuedMessages sets cc address to second guarantor email when "to" takes first guarantor email'
1100     );
1101
1102     is( $email_object->email->header('To'), $guarantor1->email, "mailto correctly uses first guarantor" );
1103     is( $email_object->email->header('Cc'), $guarantor2->email, "cc correctly uses second guarantor" );
1104
1105     # reset message - testing borrower with valid email
1106     Koha::Notice::Messages->find($message_id)->delete;
1107     $message_id = C4::Letters::EnqueueLetter($my_message);
1108
1109     $patron->email('patron@example.com')->store();
1110
1111     warning_like { C4::Letters::SendQueuedMessages(); }
1112     qr|Fake send_or_die|,
1113         "SendQueuedMessages is using the mocked send_or_die routine";
1114
1115     $message = $schema->resultset('MessageQueue')->search(
1116         {
1117             borrowernumber => $borrowernumber,
1118             status         => 'sent'
1119         }
1120     )->next();
1121
1122     is(
1123         $message->to_address(),
1124         $patron->email,
1125         'SendQueuedMessages uses patron email when defined'
1126     );
1127
1128     is(
1129         $message->cc_address(),
1130         $guarantor1->email.",".$guarantor2->email,
1131         'SendQueuedMessages sets cc address to both guarantor emails when patron has email defined'
1132     );
1133
1134     is( $email_object->email->header('To'), $patron->email, "mailto correctly uses patrons email address" );
1135     is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1136
1137
1138     # reset message - testing explicit to passed to enqueue
1139     Koha::Notice::Messages->find($message_id)->delete;
1140     $my_message->{'to_address'} = 'to@example.com';
1141     $message_id = C4::Letters::EnqueueLetter($my_message);
1142
1143     warning_like { C4::Letters::SendQueuedMessages(); }
1144     qr|Fake send_or_die|,
1145         "SendQueuedMessages is using the mocked send_or_die routine";
1146
1147     $message = $schema->resultset('MessageQueue')->search(
1148         {
1149             borrowernumber => $borrowernumber,
1150             status         => 'sent'
1151         }
1152     )->next();
1153
1154     is(
1155         $message->to_address(),
1156         'to@example.com',
1157         'SendQueuedMessages uses to_address if it was specified at enqueue time'
1158     );
1159
1160     is(
1161         $message->cc_address(),
1162         $guarantor1->email.",".$guarantor2->email,
1163         'SendQueuedMessages sets cc address to both guarantor emails when "to" is already specified'
1164     );
1165
1166     is( $email_object->email->header('To'), 'to@example.com', "mailto correctly uses passed email" );
1167     is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1168
1169     # clear borrower queue
1170     Koha::Notice::Messages->find($message_id)->delete;
1171
1172     t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
1173 };
1174
1175 subtest 'get_item_content' => sub {
1176     plan tests => 2;
1177
1178     t::lib::Mocks::mock_preference('dateformat', 'metric');
1179     t::lib::Mocks::mock_preference('timeformat', '24hr');
1180     my @items = (
1181         {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
1182         {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
1183     );
1184     my @item_content_fields = qw( date_due title barcode author itemnumber );
1185
1186     my $items_content;
1187     for my $item ( @items ) {
1188         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
1189     }
1190
1191     my $expected_items_content = <<EOF;
1192 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
1193 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
1194 EOF
1195     is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
1196
1197
1198     $items_content = q||;
1199     for my $item ( @items ) {
1200         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
1201     }
1202
1203     $expected_items_content = <<EOF;
1204 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
1205 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
1206 EOF
1207     is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
1208 };
1209
1210 subtest 'Test where parameter for SendQueuedMessages' => sub {
1211     plan tests => 3;
1212
1213     my $dbh = C4::Context->dbh;
1214
1215     my $borrowernumber = Koha::Patron->new({
1216         firstname    => 'Jane',
1217         surname      => 'Smith',
1218         categorycode => $patron_category,
1219         branchcode   => $library->{branchcode},
1220         dateofbirth  => $date,
1221     })->store->borrowernumber;
1222
1223     $dbh->do(q|DELETE FROM message_queue|);
1224     $my_message = {
1225         'letter' => {
1226             'content'      => 'a message',
1227             'metadata'     => 'metadata',
1228             'code'         => 'TEST_MESSAGE',
1229             'content_type' => 'text/plain',
1230             'title'        => 'message title'
1231         },
1232         'borrowernumber'         => $borrowernumber,
1233         'to_address'             => undef,
1234         'message_transport_type' => 'sms',
1235         'from_address'           => 'from@example.com'
1236     };
1237     my $my_message2 = {
1238         'letter' => {
1239             'content'      => 'another message',
1240             'metadata'     => 'metadata',
1241             'code'         => 'TEST_MESSAGE',
1242             'content_type' => 'text/plain',
1243             'title'        => 'message title'
1244         },
1245         'borrowernumber'         => $borrowernumber,
1246         'to_address'             => undef,
1247         'message_transport_type' => 'sms',
1248         'from_address'           => 'from@example.com'
1249     };
1250     my $my_message3 = {
1251         'letter' => {
1252             'content'      => 'a skipped message',
1253             'metadata'     => 'metadata',
1254             'code'         => 'TEST_MESSAGE',
1255             'content_type' => 'text/plain',
1256             'title'        => 'message title'
1257         },
1258         'borrowernumber'         => $borrowernumber,
1259         'to_address'             => undef,
1260         'message_transport_type' => 'sms',
1261         'from_address'           => 'from@example.com'
1262     };
1263     my @id = ( C4::Letters::EnqueueLetter($my_message),
1264         C4::Letters::EnqueueLetter($my_message2),
1265         C4::Letters::EnqueueLetter($my_message3),
1266     );
1267     C4::Letters::SendQueuedMessages({
1268         # Test scalar/arrayref in parameter too
1269         letter_code => [ 'TEST_MESSAGE', 'SOMETHING_NOT_THERE' ],
1270         type => 'sms',
1271         where => q{content NOT LIKE '%skipped%'},
1272     });
1273     is( Koha::Notice::Messages->find( $id[0] )->status, 'failed', 'Processed but failed' );
1274     is( Koha::Notice::Messages->find( $id[1] )->status, 'failed', 'Processed but failed' );
1275     is( Koha::Notice::Messages->find( $id[2] )->status, 'pending', 'Skipped, still pending' );
1276 };
1277
1278 subtest 'Test limit parameter for SendQueuedMessages' => sub {
1279     plan tests => 18;
1280
1281     my $dbh = C4::Context->dbh;
1282
1283     my $borrowernumber = Koha::Patron->new({
1284         firstname    => 'Jane',
1285         surname      => 'Smith',
1286         categorycode => $patron_category,
1287         branchcode   => $library->{branchcode},
1288         dateofbirth  => $date,
1289         email          => 'shouldnotwork@wrong.net',
1290     })->store->borrowernumber;
1291
1292     $dbh->do(q|DELETE FROM message_queue|);
1293     $my_message = {
1294         'letter' => {
1295             'content'      => 'a message',
1296             'metadata'     => 'metadata',
1297             'code'         => 'TEST_MESSAGE',
1298             'content_type' => 'text/plain',
1299             'title'        => 'message title'
1300         },
1301         'borrowernumber'         => $borrowernumber,
1302         'to_address'             => undef,
1303         'message_transport_type' => 'email',
1304         'from_address'           => 'from@example.com'
1305     };
1306     C4::Letters::EnqueueLetter($my_message) for 1..5;
1307
1308     $send_or_die_count = 0; # reset
1309     my $messages_sent;
1310     my $regex = qr|Fake send_or_die|;
1311     warning_like {
1312         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 1 } ) }
1313         $regex,
1314         "SendQueuedMessages with limit 1";
1315     is( $messages_sent, 1,
1316         'Sent 1 message with limit of 1 and 5 unprocessed messages' );
1317
1318     warnings_like {
1319         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 2 } ) }
1320         [ map { $regex } 1..2 ],
1321         "SendQueuedMessages with limit 2";
1322     is( $messages_sent, 2,
1323         'Sent 2 messages with limit of 2 and 4 unprocessed messages' );
1324
1325     warnings_like {
1326         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 3 } ) }
1327         [ map { $regex } 1..2 ],
1328         "SendQueuedMessages with limit 3";
1329     is( $messages_sent, 2,
1330         'Sent 2 messages with limit of 3 and 2 unprocessed messages' );
1331
1332     is( $send_or_die_count, 5, '5 messages sent' );
1333     # Mimic correct status in queue for next tests
1334     Koha::Notice::Messages->search({ to_address => { 'LIKE', '%wrong.net' }})->update({ status => 'sent' });
1335
1336     # Now add a few domain limits too, sending 2 more mails to wrongnet, 2 to fake1, 2 to fake2
1337     # Since we already sent 5 to wrong.net, we expect one deferral when limiting to 6
1338     # Similarly we arrange for 1 deferral for fake1, and 2 for fake2
1339     # We set therefore limit to 3 sent messages: we expect 2 sent, 4 deferred (so 6 processed)
1340     t::lib::Mocks::mock_config( 'message_domain_limits', { domain => [
1341         { name => 'wrong.net',    limit => 6, unit => '1m' },
1342         { name => 'fake1.domain', limit => 1, unit => '1m' },
1343         { name => 'fake2.domain', limit => 0, unit => '1m' },
1344     ]});
1345     C4::Letters::EnqueueLetter($my_message) for 1..2;
1346     $my_message->{to_address} = 'someone@fake1.domain';
1347     C4::Letters::EnqueueLetter($my_message) for 1..2;
1348     $my_message->{to_address} = 'another@fake2.domain';
1349     C4::Letters::EnqueueLetter($my_message) for 1..2;
1350     my $mocked_util = Test::MockModule->new('Koha::Notice::Util');
1351     my $count_exceeds_calls = 0;
1352     $mocked_util->mock( 'exceeds_limit', sub {
1353         $count_exceeds_calls++;
1354         $mocked_util->original('exceeds_limit')->(@_);
1355     });
1356     warnings_like {
1357         $messages_sent = C4::Letters::SendQueuedMessages({ limit => 3 }) }
1358         [ qr/wrong.net reached limit/, $regex, qr/fake1.domain reached limit/, $regex ],
1359         "SendQueuedMessages with limit 2 and domain limits";
1360     is( $messages_sent, 2, 'Only expecting 2 sent messages' );
1361     is(  Koha::Notice::Messages->search({ status => 'pending' })->count, 4, 'Still 4 pending' );
1362     is( $count_exceeds_calls, 6, 'We saw 6 messages while checking domain limits: so we deferred 4' );
1363 };
1364
1365 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1366
1367     plan tests => 8;
1368
1369     my $dbh = C4::Context->dbh;
1370
1371     my $borrowernumber = Koha::Patron->new({
1372         firstname    => 'Jane',
1373         surname      => 'Smith',
1374         categorycode => $patron_category,
1375         branchcode   => $library->{branchcode},
1376         dateofbirth  => $date,
1377         smsalertnumber => undef,
1378     })->store->borrowernumber;
1379
1380     $dbh->do(q|DELETE FROM message_queue|);
1381     $my_message = {
1382         'letter' => {
1383             'content'      => 'a message',
1384             'metadata'     => 'metadata',
1385             'code'         => 'TEST_MESSAGE',
1386             'content_type' => 'text/plain',
1387             'title'        => 'message title'
1388         },
1389         'borrowernumber'         => $borrowernumber,
1390         'to_address'             => 'to@example.org',
1391         'message_transport_type' => 'email',
1392         'from_address'           => '@example.com' # invalid from_address
1393     };
1394     my $message_id = C4::Letters::EnqueueLetter($my_message);
1395     $send_or_die_count = 0; # reset
1396     my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1397     is( $send_or_die_count, 0, 'Nothing sent when one message_id passed' );
1398     my $message_1 = C4::Letters::GetMessage($message_id);
1399     is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1400     is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1401
1402     $my_message->{from_address} = 'root@example.org'; # valid from_address
1403     $message_id = C4::Letters::EnqueueLetter($my_message);
1404     warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1405         qr|Fake send_or_die|,
1406         "SendQueuedMessages is using the mocked send_or_die routine";
1407     is( $send_or_die_count, 1, 'One message passed through' );
1408     $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1409     my $message_2 = C4::Letters::GetMessage($message_id);
1410     is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1411     is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
1412 };
1413
1414 subtest 'Template toolkit syntax in parameters' => sub {
1415
1416     my $borrowernumber = Koha::Patron->new(
1417         {
1418             firstname      => 'Robert',
1419             surname        => '[% USE Categories %][% Categories.all().search_related("borrowers").count() %]',
1420             categorycode   => $patron_category,
1421             branchcode     => $library->{branchcode},
1422             dateofbirth    => $date,
1423             smsalertnumber => undef,
1424         }
1425     )->store->borrowernumber;
1426
1427     my $title   = q|<<branches.branchname>> - <<status>>|;
1428     my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>};
1429
1430     $dbh->do(
1431         q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES (?,'my module','tt test','my name',1,?,?,'email')|,
1432         undef, $library->{branchcode}, $title, $content
1433     );
1434
1435     my $tables = {
1436         borrowers => $borrowernumber,
1437         branches  => $library->{branchcode},
1438         biblio    => $biblionumber,
1439     };
1440     my $substitute = {
1441         status => 'overdue',
1442     };
1443     my $prepared_letter = GetPreparedLetter(
1444         module      => 'my module',
1445         branchcode  => $library->{branchcode},
1446         letter_code => 'tt test',
1447         tables      => $tables,
1448         substitute  => $substitute,
1449         repeat      => [],
1450     );
1451
1452     is(
1453         $prepared_letter->{content},
1454         'Dear Robert [% USE Categories %][% Categories.all().search_related("borrowers").count() %]',
1455         'Template toolkit syntax in parameter was not evaluated.'
1456     );
1457 };