Bug 8838: Add digest option for HOLD notice
[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 => 94;
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
251 my $sms_content = 'This is a SMS for an <<status>>';
252 $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 );
253
254 my $tables = {
255     borrowers => $borrowernumber,
256     branches => $library->{branchcode},
257     biblio => $biblionumber,
258 };
259 my $substitute = {
260     status => 'overdue',
261 };
262 my $repeat = [
263     {
264         itemcallnumber => 'my callnumber1',
265         barcode        => '1234',
266     },
267     {
268         itemcallnumber => 'my callnumber2',
269         barcode        => '5678',
270     },
271 ];
272 my $prepared_letter = GetPreparedLetter((
273     module      => 'my module',
274     branchcode  => $library->{branchcode},
275     letter_code => 'my code',
276     tables      => $tables,
277     substitute  => $substitute,
278     repeat      => $repeat,
279 ));
280 my $retrieved_library = Koha::Libraries->find($library->{branchcode});
281 my $my_title_letter = $retrieved_library->branchname . qq| - $substitute->{status}|;
282 my $biblio_timestamp = dt_from_string( GetBiblioData($biblionumber)->{timestamp} );
283 my $my_content_letter = qq|Dear Jane Smith,
284 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.
285
286 |.$retrieved_library->branchname.qq|
287 |.$retrieved_library->branchaddress1.qq|
288 URL: http://thisisatest.com
289
290 The following item(s) is/are currently $substitute->{status}:
291
292 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
293 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
294
295 Thank-you for your prompt attention to this matter.
296 Don't forget your date of birth: | . output_pref({ dt => $date, dateonly => 1 }) . q|.
297 Look at this wonderful biblio timestamp: | . output_pref({ dt => $biblio_timestamp })  . ".\n";
298
299 is( $prepared_letter->{title}, $my_title_letter, 'GetPreparedLetter returns the title correctly' );
300 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
301
302 $prepared_letter = GetPreparedLetter((
303     module                 => 'my module',
304     branchcode             => $library->{branchcode},
305     letter_code            => 'my code',
306     tables                 => $tables,
307     substitute             => $substitute,
308     repeat                 => $repeat,
309     message_transport_type => 'sms',
310 ));
311 $my_content_letter = qq|This is a SMS for an $substitute->{status}|;
312 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
313
314 warning_is {
315     $prepared_letter = GetPreparedLetter((
316         module                 => 'my module',
317         branchcode             => $library->{branchcode},
318         letter_code            => 'my code',
319         tables                 => $tables,
320         substitute             => { status => undef },
321         repeat                 => $repeat,
322         message_transport_type => 'sms',
323     ));
324 }
325 undef, "No warning if GetPreparedLetter called with substitute containing undefined value";
326 is( $prepared_letter->{content}, q|This is a SMS for an |, 'GetPreparedLetter returns the content correctly when substitute contains undefined value' );
327
328 $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>>.');});
329 $prepared_letter = GetPreparedLetter((
330     module                 => 'test_date',
331     branchcode             => '',
332     letter_code            => 'test_date',
333     tables                 => $tables,
334     substitute             => $substitute,
335     repeat                 => $repeat,
336 ));
337 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
338
339 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp | dateonly>>.' WHERE code = 'test_date';});
340 $prepared_letter = GetPreparedLetter((
341     module                 => 'test_date',
342     branchcode             => '',
343     letter_code            => 'test_date',
344     tables                 => $tables,
345     substitute             => $substitute,
346     repeat                 => $repeat,
347 ));
348 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
349
350 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp|dateonly >>.' WHERE code = 'test_date';});
351 $prepared_letter = GetPreparedLetter((
352     module                 => 'test_date',
353     branchcode             => '',
354     letter_code            => 'test_date',
355     tables                 => $tables,
356     substitute             => $substitute,
357     repeat                 => $repeat,
358 ));
359 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
360
361 t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
362 my $yesterday_night = $date->clone->add( days => -1 )->set_hour(22);
363 $dbh->do(q|UPDATE biblio SET timestamp = ? WHERE biblionumber = ?|, undef, $yesterday_night, $biblionumber );
364 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp>>.' WHERE code = 'test_date';});
365 $prepared_letter = GetPreparedLetter((
366     module                 => 'test_date',
367     branchcode             => '',
368     letter_code            => 'test_date',
369     tables                 => $tables,
370     substitute             => $substitute,
371     repeat                 => $repeat,
372 ));
373 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
374
375 $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>');});
376 $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 %]');});
377
378 my $testacqorder2_content = <<EOF;
379 [%- USE Price -%]
380 [% bookseller.name %]
381 [% FOREACH order IN orders %]
382 Ordernumber [% order.ordernumber %] [% order.quantity %] [% order.listprice | \$Price %]
383 [% END %]
384 EOF
385
386 $dbh->do("INSERT INTO letter (module, code, name, title, content) VALUES ('orderacquisition','TESTACQORDER2','Acquisition Order','Order','$testacqorder2_content');");
387
388 my $popito = $builder->build({
389     source => 'Aqbookseller',
390     value  => { name => 'Popito' }
391 });
392
393 my $order_1 = $builder->build({
394     source => 'Aqorder',
395     value  => {
396        quantity => 2,
397        listprice => '12.00'
398     }
399 });
400
401 my $order_2 = $builder->build({
402     source => 'Aqorder',
403     value  => {
404        quantity => 1,
405        listprice => '23.50'
406     }
407 });
408
409 $prepared_letter = GetPreparedLetter((
410     module                 => 'orderacquisition',
411     branchcode             => '',
412     letter_code            => 'TESTACQORDER2',
413     tables                 => { 'aqbooksellers' => $popito->{id} },
414     loops                  => {
415         aqorders => [ $order_1->{ordernumber}, $order_2->{ordernumber} ]
416     }
417 ));
418
419 my $testacqorder2_expected = qq|Popito
420
421 Ordernumber | . $order_1->{ordernumber} . qq| 2 12.00
422
423 Ordernumber | . $order_2->{ordernumber} . qq| 1 23.50
424
425 |;
426
427 is($prepared_letter->{content}, $testacqorder2_expected);
428
429 # Test that _parseletter doesn't modify its parameters bug 15429
430 {
431     my $values = { dateexpiry => '2015-12-13', };
432     C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
433     is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
434 }
435
436 # Correctly format dateexpiry
437 {
438     my $values = { dateexpiry => '2015-12-13', };
439
440     t::lib::Mocks::mock_preference('dateformat', 'metric');
441     t::lib::Mocks::mock_preference('timeformat', '24hr');
442     my $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
443     is( $letter->{content}, 'expiry on 13/12/2015' );
444
445     t::lib::Mocks::mock_preference('dateformat', 'metric');
446     t::lib::Mocks::mock_preference('timeformat', '12hr');
447     $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
448     is( $letter->{content}, 'expiry on 13/12/2015' );
449 }
450
451 my $bookseller = Koha::Acquisition::Bookseller->new(
452     {
453         name => "my vendor",
454         address1 => "bookseller's address",
455         phone => "0123456",
456         active => 1,
457         deliverytime => 5,
458     }
459 )->store;
460 my $booksellerid = $bookseller->id;
461
462 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith',  phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
463 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues      => 1, booksellerid => $booksellerid } )->store;
464 my $basketno = NewBasket($booksellerid, 1, 'The basket name');
465
466 my $budgetid = C4::Budgets::AddBudget({
467     budget_code => "budget_code_test_letters",
468     budget_name => "budget_name_test_letters",
469 });
470
471 my $bib = MARC::Record->new();
472 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
473     $bib->append_fields(
474         MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
475     );
476 } else {
477     $bib->append_fields(
478         MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
479     );
480 }
481
482 my $logged_in_user = $builder->build_object(
483     {
484         class => 'Koha::Patrons',
485         value => {
486             branchcode => $library->{branchcode},
487             email      => 'some@email.com'
488         }
489     }
490 );
491
492 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
493
494 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
495 my $order = Koha::Acquisition::Order->new(
496     {
497         basketno => $basketno,
498         quantity => 1,
499         biblionumber => $biblionumber,
500         budget_id => $budgetid,
501     }
502 )->store;
503 my $ordernumber = $order->ordernumber;
504
505 Koha::Acquisition::Baskets->find( $basketno )->close;
506 my $err;
507 warning_like {
508     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
509     qr/^Bookseller .* without emails at/,
510     "SendAlerts prints a warning";
511 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
512
513 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
514 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
515
516 # Ensure that the preference 'ClaimsLog' is set to logging
517 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
518
519 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
520 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
521
522 {
523 warning_like {
524     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
525     qr|Fake send_or_die|,
526     "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
527 is($err, 1, "Successfully sent order.");
528 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent order");
529 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered) Basket name: The basket name', 'Order notice text constructed successfully');
530
531 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
532 $mocked_koha_email->mock( 'send_or_die', sub {
533     Email::Sender::Failure->throw('something went wrong');
534 });
535
536 warning_like {
537     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ); }
538     qr{something went wrong},
539     'Warning is printed';
540
541 is($err->{error}, 'something went wrong', "Send exception, error message returned");
542
543 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
544 warning_like {
545     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
546     qr/No orderacquisition TESTACQORDER letter transported by email/,
547     "GetPreparedLetter warns about missing notice template";
548 is($err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined.");
549 }
550
551 {
552 warning_like {
553     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
554     qr|Fake send_or_die|,
555     "SendAlerts is using the mocked send_or_die routine";
556
557 is($err, 1, "Successfully sent claim");
558 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
559 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
560
561 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
562 $mocked_koha_email->mock( 'send_or_die', sub {
563     Email::Sender::Failure->throw('something went wrong');
564 });
565
566 warning_like {
567     $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
568     qr{something went wrong},
569     'Warning is printed';
570
571 is($err->{error}, 'something went wrong', "Send exception, error message returned");
572 }
573
574 {
575 use C4::Serials qw( NewSubscription GetSerials findSerialsByStatus ModSerialStatus );
576
577 my $notes = 'notes';
578 my $internalnotes = 'intnotes';
579 my $number_pattern = $builder->build_object(
580     {
581         class => 'Koha::Subscription::Numberpatterns',
582         value => { numberingmethod => 'No. {X}' }
583     }
584 );
585 my $subscriptionid = NewSubscription(
586      undef,      "",     undef, undef, undef, $biblionumber,
587     '2013-01-01', 1, undef, undef,  undef,
588     undef,      undef,  undef, undef, undef, undef,
589     1,          $notes,undef, '2013-01-01', undef, $number_pattern->id,
590     undef,       undef,  0,    $internalnotes,  0,
591     undef, undef, 0,          undef,         '2013-12-31', 0
592 );
593 $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>>');});
594 my ($serials_count, @serials) = GetSerials($subscriptionid);
595 my $serial = $serials[0];
596
597 my $patron = Koha::Patron->new({
598     firstname    => 'John',
599     surname      => 'Smith',
600     categorycode => $patron_category,
601     branchcode   => $library->{branchcode},
602     dateofbirth  => $date,
603     email        => 'john.smith@test.de',
604 })->store;
605 my $borrowernumber = $patron->borrowernumber;
606 my $subscription = Koha::Subscriptions->find( $subscriptionid );
607 $subscription->add_subscriber( $patron );
608
609 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
610 my $err2;
611 warning_like {
612 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
613     qr|Fake send_or_die|,
614     "SendAlerts is using the mocked send_or_die routine";
615
616 is($err2, 1, "Successfully sent serial notification");
617 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
618 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
619
620 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
621
622 my $err3;
623 warning_like {
624 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
625     qr|Fake send_or_die|,
626     "SendAlerts is using the mocked send_or_die routine";
627 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
628
629 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
630 $mocked_koha_email->mock( 'send_or_die', sub {
631     Email::Sender::Failure->throw('something went wrong');
632 });
633
634 warning_like {
635     $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
636     qr{something went wrong},
637     'Warning is printed';
638
639 is($err->{error}, 'something went wrong', "Send exception, error message returned");
640
641 }
642 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
643
644 subtest '_parseletter' => sub {
645     plan tests => 2;
646
647     # Regression test for bug 10843
648     # $dt->add takes a scalar, not undef
649     my $letter;
650     t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay', undef );
651     $letter = C4::Letters::_parseletter( undef, 'reserves', { waitingdate => "2013-01-01" } );
652     is( ref($letter), 'HASH' );
653     t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay', 1 );
654     $letter = C4::Letters::_parseletter( undef, 'reserves', { waitingdate => "2013-01-01" } );
655     is( ref($letter), 'HASH' );
656 };
657
658 subtest 'SendAlerts - claimissue' => sub {
659     plan tests => 13;
660
661     use C4::Serials;
662
663     $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>>');});
664
665     my $bookseller = Koha::Acquisition::Bookseller->new(
666         {
667             name => "my vendor",
668             address1 => "bookseller's address",
669             phone => "0123456",
670             active => 1,
671             deliverytime => 5,
672         }
673     )->store;
674     my $booksellerid = $bookseller->id;
675
676     Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
677
678     my $bib = MARC::Record->new();
679     if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
680         $bib->append_fields(
681             MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
682             MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
683         );
684     } else {
685         $bib->append_fields(
686             MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
687             MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
688         );
689     }
690     my ($biblionumber) = AddBiblio($bib, '');
691
692     my $number_pattern = $builder->build_object(
693         {
694             class => 'Koha::Subscription::Numberpatterns',
695             value => { numberingmethod => 'No. {X}' }
696         }
697     );
698
699     my $subscriptionid = NewSubscription(
700          undef, "", $booksellerid, undef, undef, $biblionumber,
701         '2013-01-01', 1, undef, undef,  undef,
702         undef,  undef,  undef, undef, undef, undef,
703         1, 'public',undef, '2013-01-01', undef, $number_pattern->id,
704         undef, undef,  0, 'internal',  0,
705         undef, undef, 0,  undef, '2013-12-31', 0
706     );
707
708     my ($serials_count, @serials) = GetSerials($subscriptionid);
709     my  @serialids = ($serials[0]->{serialid});
710
711     my $err;
712     warning_like {
713         $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
714         qr/^Bookseller .* without emails at/,
715         "Warn on vendor without email address";
716
717     $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
718     $bookseller->contacts->next->email('testemail@mydomain.com')->store;
719
720     # Ensure that the preference 'ClaimsLog' is set to logging
721     t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
722
723     # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
724     t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
725
726     t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
727
728     {
729     warning_like {
730         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
731         qr|Fake send_or_die|,
732         "SendAlerts is using the mocked send_or_die routine (claimissues)";
733     is( $err, 1, "Successfully sent claim" );
734     is( $email_object->email->header('To'),
735         'testemail@mydomain.com', "mailto correct in sent claim" );
736     is(
737         $email_object->email->body,
738         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
739         'Serial claim letter for 1 issue constructed successfully'
740     );
741
742     my $mocked_koha_email = Test::MockModule->new('Koha::Email');
743     $mocked_koha_email->mock( 'send_or_die', sub {
744             Email::Sender::Failure->throw('something went wrong');
745     });
746
747     warning_like {
748         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
749         qr{something went wrong},
750         'Warning is printed';
751
752     is($err->{error}, 'something went wrong', "Send exception, error message returned");
753     }
754
755     {
756     my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
757     my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
758     ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
759     ($serials_count, @serials) = GetSerials($subscriptionid);
760     push @serialids, ($serials[1]->{serialid});
761
762     warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
763         qr|Fake send_or_die|,
764         "SendAlerts is using the mocked send_or_die routine (claimissues)";
765
766     is(
767         $email_object->email->body,
768         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
769           . $email_object->email->crlf
770           . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
771         "Serial claim letter for 2 issues constructed successfully"
772     );
773
774     $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
775     warning_like {
776         $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
777         qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
778         "GetPreparedLetter warns about missing notice template";
779     is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
780     }
781
782 };
783
784 subtest 'GetPreparedLetter' => sub {
785     plan tests => 4;
786
787     Koha::Notice::Template->new(
788         {
789             module                 => 'test',
790             code                   => 'test',
791             branchcode             => '',
792             message_transport_type => 'email'
793         }
794     )->store;
795     my $letter;
796     warning_like {
797         $letter = C4::Letters::GetPreparedLetter(
798             module      => 'test',
799             letter_code => 'test',
800         );
801     }
802     qr{^ERROR: nothing to substitute},
803 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
804     is( $letter, undef,
805 'No letter should be returned by GetPreparedLetter if something went wrong'
806     );
807
808     warning_like {
809         $letter = C4::Letters::GetPreparedLetter(
810             module      => 'test',
811             letter_code => 'test',
812             substitute  => {}
813         );
814     }
815     qr{^ERROR: nothing to substitute},
816 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
817     is( $letter, undef,
818 'No letter should be returned by GetPreparedLetter if something went wrong'
819     );
820
821 };
822
823
824
825 subtest 'TranslateNotices' => sub {
826     plan tests => 4;
827
828     t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
829
830     $dbh->do(
831         q|
832         INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
833         ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
834         ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
835     | );
836     my $substitute = {};
837     my $letter = C4::Letters::GetPreparedLetter(
838             module                 => 'test',
839             tables                 => $tables,
840             letter_code            => 'code',
841             message_transport_type => 'email',
842             substitute             => $substitute,
843     );
844     is(
845         $letter->{title},
846         'a test',
847         'GetPreparedLetter should return the default one if the lang parameter is not provided'
848     );
849
850     $letter = C4::Letters::GetPreparedLetter(
851             module                 => 'test',
852             tables                 => $tables,
853             letter_code            => 'code',
854             message_transport_type => 'email',
855             substitute             => $substitute,
856             lang                   => 'es-ES',
857     );
858     is( $letter->{title}, 'una prueba',
859         'GetPreparedLetter should return the required notice if it exists' );
860
861     $letter = C4::Letters::GetPreparedLetter(
862             module                 => 'test',
863             tables                 => $tables,
864             letter_code            => 'code',
865             message_transport_type => 'email',
866             substitute             => $substitute,
867             lang                   => 'fr-FR',
868     );
869     is(
870         $letter->{title},
871         'a test',
872         'GetPreparedLetter should return the default notice if the one required does not exist'
873     );
874
875     t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
876
877     $letter = C4::Letters::GetPreparedLetter(
878             module                 => 'test',
879             tables                 => $tables,
880             letter_code            => 'code',
881             message_transport_type => 'email',
882             substitute             => $substitute,
883             lang                   => 'es-ES',
884     );
885     is( $letter->{title}, 'a test',
886         'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
887
888 };
889
890 subtest 'Test SMS handling in SendQueuedMessages' => sub {
891
892     plan tests => 14;
893
894     t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
895     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
896
897     my $patron = Koha::Patrons->find($borrowernumber);
898     $dbh->do(q|
899         INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
900         VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
901         |, undef, $borrowernumber
902     );
903     eval { C4::Letters::SendQueuedMessages(); };
904     is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
905
906     my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
907     $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
908     $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
909
910     warning_like { C4::Letters::SendQueuedMessages(); }
911         qr|Fake send_or_die|,
912         "SendAlerts is using the mocked send_or_die routine (claimissues)";
913
914     my $message = $schema->resultset('MessageQueue')->search({
915         borrowernumber => $borrowernumber,
916         status => 'sent'
917     })->next();
918
919     is( $message->letter_id, $messages->[0]->{id}, "Message letter_id is set correctly" );
920     is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
921     is(
922         $message->from_address(),
923         'from@example.com',
924         'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
925     );
926
927     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
928
929     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
930
931     $message_id = C4::Letters::EnqueueLetter($my_message);
932     warning_like { C4::Letters::SendQueuedMessages(); }
933         qr|Fake send_or_die|,
934         "SendAlerts is using the mocked send_or_die routine (claimissues)";
935
936     $message = $schema->resultset('MessageQueue')->search({
937         borrowernumber => $borrowernumber,
938         status => 'sent'
939     })->next();
940
941     is(
942         $message->from_address(),
943         'override@example.com',
944         'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
945     );
946
947     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
948     $my_message->{to_address} = 'fixme@kidclamp.iswrong';
949     $message_id = C4::Letters::EnqueueLetter($my_message);
950
951     my $number_attempted = C4::Letters::SendQueuedMessages({
952         borrowernumber => -1, # -1 still triggers the borrowernumber condition
953         letter_code    => 'PASSWORD_RESET',
954     });
955     is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
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 $sms_message_address = $schema->resultset('MessageQueue')->search({
962         borrowernumber => $borrowernumber,
963         status => 'sent'
964     })->next()->to_address();
965     is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
966
967     # Test using SMS::Send::Test driver that's bundled with SMS::Send
968     t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
969
970     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
971     C4::Letters::EnqueueLetter($my_message);
972     C4::Letters::SendQueuedMessages();
973
974     $sms_message_address = $schema->resultset('MessageQueue')->search({
975         borrowernumber => $borrowernumber,
976         status => 'sent'
977     })->next()->to_address();
978     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' );
979
980     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
981 };
982
983 subtest 'Test guarantor handling in SendQueuedMessages' => sub {
984
985     plan tests => 19;
986
987     t::lib::Mocks::mock_preference( 'borrowerRelationship', 'test' );
988
989     my $patron     = Koha::Patrons->find($borrowernumber);
990     my $guarantor1 = $builder->build_object( { class => 'Koha::Patrons', value => { email => 'g1@email.com' } } );
991     my $guarantor2 = $builder->build_object( { class => 'Koha::Patrons', value => { email => 'g2@email.com' } } );
992     $patron->add_guarantor( { guarantor_id => $guarantor1->borrowernumber, relationship => 'test' } );
993     $patron->add_guarantor( { guarantor_id => $guarantor2->borrowernumber, relationship => 'test' } );
994
995     $my_message = {
996         'letter' => {
997             'content'      => 'a message',
998             'metadata'     => 'metadata',
999             'code'         => 'TEST_MESSAGE',
1000             'content_type' => 'text/plain',
1001             'title'        => 'message title'
1002         },
1003         'borrowernumber'         => $borrowernumber,
1004         'to_address'             => undef,
1005         'message_transport_type' => 'email',
1006         'from_address'           => 'from@example.com'
1007     };
1008     $message_id = C4::Letters::EnqueueLetter($my_message);
1009
1010     # feature disabled
1011     t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '0' );
1012
1013     warning_like { C4::Letters::SendQueuedMessages(); }
1014     qr|No 'to_address', email address or guarantors email address for borrowernumber|,
1015         "SendQueuedMessages fails when no to_address, patron notice email and RedirectGuaranteeEmail is not set";
1016
1017     # feature enabled
1018     t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '1' );
1019
1020     # reset message - testing without to or borrower valid email
1021     Koha::Notice::Messages->find($message_id)->delete;
1022     $message_id = C4::Letters::EnqueueLetter($my_message);
1023
1024     warning_like { C4::Letters::SendQueuedMessages(); }
1025     qr|Fake send_or_die|,
1026         "SendQueuedMessages is using the mocked send_or_die routine";
1027
1028     $message = $schema->resultset('MessageQueue')->search(
1029         {
1030             borrowernumber => $borrowernumber,
1031             status         => 'sent'
1032         }
1033     )->next();
1034
1035     is(
1036         $message->to_address(),
1037         $guarantor1->email,
1038         'SendQueuedMessages uses first guarantor email for "to" when patron has no email'
1039     );
1040
1041     is(
1042         $message->cc_address(),
1043         $guarantor2->email,
1044         'SendQueuedMessages sets cc address to second guarantor email when "to" takes first guarantor email'
1045     );
1046
1047     is( $email_object->email->header('To'), $guarantor1->email, "mailto correctly uses first guarantor" );
1048     is( $email_object->email->header('Cc'), $guarantor2->email, "cc correctly uses second guarantor" );
1049
1050     # reset message - testing borrower with valid email
1051     Koha::Notice::Messages->find($message_id)->delete;
1052     $message_id = C4::Letters::EnqueueLetter($my_message);
1053
1054     $patron->email('patron@example.com')->store();
1055
1056     warning_like { C4::Letters::SendQueuedMessages(); }
1057     qr|Fake send_or_die|,
1058         "SendQueuedMessages is using the mocked send_or_die routine";
1059
1060     $message = $schema->resultset('MessageQueue')->search(
1061         {
1062             borrowernumber => $borrowernumber,
1063             status         => 'sent'
1064         }
1065     )->next();
1066
1067     is(
1068         $message->to_address(),
1069         $patron->email,
1070         'SendQueuedMessages uses patron email when defined'
1071     );
1072
1073     is(
1074         $message->cc_address(),
1075         $guarantor1->email.",".$guarantor2->email,
1076         'SendQueuedMessages sets cc address to both guarantor emails when patron has email defined'
1077     );
1078
1079     is( $email_object->email->header('To'), $patron->email, "mailto correctly uses patrons email address" );
1080     is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1081
1082
1083     # reset message - testing explicit to passed to enqueue
1084     Koha::Notice::Messages->find($message_id)->delete;
1085     $my_message->{'to_address'} = 'to@example.com';
1086     $message_id = C4::Letters::EnqueueLetter($my_message);
1087
1088     warning_like { C4::Letters::SendQueuedMessages(); }
1089     qr|Fake send_or_die|,
1090         "SendQueuedMessages is using the mocked send_or_die routine";
1091
1092     $message = $schema->resultset('MessageQueue')->search(
1093         {
1094             borrowernumber => $borrowernumber,
1095             status         => 'sent'
1096         }
1097     )->next();
1098
1099     is(
1100         $message->to_address(),
1101         'to@example.com',
1102         'SendQueuedMessages uses to_address if it was specified at enqueue time'
1103     );
1104
1105     is(
1106         $message->cc_address(),
1107         $guarantor1->email.",".$guarantor2->email,
1108         'SendQueuedMessages sets cc address to both guarantor emails when "to" is already specified'
1109     );
1110
1111     is( $email_object->email->header('To'), 'to@example.com', "mailto correctly uses passed email" );
1112     is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1113
1114     # clear borrower queue
1115     Koha::Notice::Messages->find($message_id)->delete;
1116 };
1117
1118 subtest 'get_item_content' => sub {
1119     plan tests => 2;
1120
1121     t::lib::Mocks::mock_preference('dateformat', 'metric');
1122     t::lib::Mocks::mock_preference('timeformat', '24hr');
1123     my @items = (
1124         {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
1125         {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
1126     );
1127     my @item_content_fields = qw( date_due title barcode author itemnumber );
1128
1129     my $items_content;
1130     for my $item ( @items ) {
1131         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
1132     }
1133
1134     my $expected_items_content = <<EOF;
1135 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
1136 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
1137 EOF
1138     is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
1139
1140
1141     $items_content = q||;
1142     for my $item ( @items ) {
1143         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
1144     }
1145
1146     $expected_items_content = <<EOF;
1147 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
1148 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
1149 EOF
1150     is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
1151 };
1152
1153 subtest 'Test where parameter for SendQueuedMessages' => sub {
1154     plan tests => 3;
1155
1156     my $dbh = C4::Context->dbh;
1157
1158     my $borrowernumber = Koha::Patron->new({
1159         firstname    => 'Jane',
1160         surname      => 'Smith',
1161         categorycode => $patron_category,
1162         branchcode   => $library->{branchcode},
1163         dateofbirth  => $date,
1164     })->store->borrowernumber;
1165
1166     $dbh->do(q|DELETE FROM message_queue|);
1167     $my_message = {
1168         'letter' => {
1169             'content'      => 'a message',
1170             'metadata'     => 'metadata',
1171             'code'         => 'TEST_MESSAGE',
1172             'content_type' => 'text/plain',
1173             'title'        => 'message title'
1174         },
1175         'borrowernumber'         => $borrowernumber,
1176         'to_address'             => undef,
1177         'message_transport_type' => 'sms',
1178         'from_address'           => 'from@example.com'
1179     };
1180     my $my_message2 = {
1181         'letter' => {
1182             'content'      => 'another message',
1183             'metadata'     => 'metadata',
1184             'code'         => 'TEST_MESSAGE',
1185             'content_type' => 'text/plain',
1186             'title'        => 'message title'
1187         },
1188         'borrowernumber'         => $borrowernumber,
1189         'to_address'             => undef,
1190         'message_transport_type' => 'sms',
1191         'from_address'           => 'from@example.com'
1192     };
1193     my $my_message3 = {
1194         'letter' => {
1195             'content'      => 'a skipped message',
1196             'metadata'     => 'metadata',
1197             'code'         => 'TEST_MESSAGE',
1198             'content_type' => 'text/plain',
1199             'title'        => 'message title'
1200         },
1201         'borrowernumber'         => $borrowernumber,
1202         'to_address'             => undef,
1203         'message_transport_type' => 'sms',
1204         'from_address'           => 'from@example.com'
1205     };
1206     my @id = ( C4::Letters::EnqueueLetter($my_message),
1207         C4::Letters::EnqueueLetter($my_message2),
1208         C4::Letters::EnqueueLetter($my_message3),
1209     );
1210     C4::Letters::SendQueuedMessages({
1211         # Test scalar/arrayref in parameter too
1212         letter_code => [ 'TEST_MESSAGE', 'SOMETHING_NOT_THERE' ],
1213         type => 'sms',
1214         where => q{content NOT LIKE '%skipped%'},
1215     });
1216     is( Koha::Notice::Messages->find( $id[0] )->status, 'failed', 'Processed but failed' );
1217     is( Koha::Notice::Messages->find( $id[1] )->status, 'failed', 'Processed but failed' );
1218     is( Koha::Notice::Messages->find( $id[2] )->status, 'pending', 'Skipped, still pending' );
1219 };
1220
1221 subtest 'Test limit parameter for SendQueuedMessages' => sub {
1222     plan tests => 18;
1223
1224     my $dbh = C4::Context->dbh;
1225
1226     my $borrowernumber = Koha::Patron->new({
1227         firstname    => 'Jane',
1228         surname      => 'Smith',
1229         categorycode => $patron_category,
1230         branchcode   => $library->{branchcode},
1231         dateofbirth  => $date,
1232         email          => 'shouldnotwork@wrong.net',
1233     })->store->borrowernumber;
1234
1235     $dbh->do(q|DELETE FROM message_queue|);
1236     $my_message = {
1237         'letter' => {
1238             'content'      => 'a message',
1239             'metadata'     => 'metadata',
1240             'code'         => 'TEST_MESSAGE',
1241             'content_type' => 'text/plain',
1242             'title'        => 'message title'
1243         },
1244         'borrowernumber'         => $borrowernumber,
1245         'to_address'             => undef,
1246         'message_transport_type' => 'email',
1247         'from_address'           => 'from@example.com'
1248     };
1249     C4::Letters::EnqueueLetter($my_message) for 1..5;
1250
1251     $send_or_die_count = 0; # reset
1252     my $messages_sent;
1253     my $regex = qr|Fake send_or_die|;
1254     warning_like {
1255         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 1 } ) }
1256         $regex,
1257         "SendQueuedMessages with limit 1";
1258     is( $messages_sent, 1,
1259         'Sent 1 message with limit of 1 and 5 unprocessed messages' );
1260
1261     warnings_like {
1262         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 2 } ) }
1263         [ map { $regex } 1..2 ],
1264         "SendQueuedMessages with limit 2";
1265     is( $messages_sent, 2,
1266         'Sent 2 messages with limit of 2 and 4 unprocessed messages' );
1267
1268     warnings_like {
1269         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 3 } ) }
1270         [ map { $regex } 1..2 ],
1271         "SendQueuedMessages with limit 3";
1272     is( $messages_sent, 2,
1273         'Sent 2 messages with limit of 3 and 2 unprocessed messages' );
1274
1275     is( $send_or_die_count, 5, '5 messages sent' );
1276     # Mimic correct status in queue for next tests
1277     Koha::Notice::Messages->search({ to_address => { 'LIKE', '%wrong.net' }})->update({ status => 'sent' });
1278
1279     # Now add a few domain limits too, sending 2 more mails to wrongnet, 2 to fake1, 2 to fake2
1280     # Since we already sent 5 to wrong.net, we expect one deferral when limiting to 6
1281     # Similarly we arrange for 1 deferral for fake1, and 2 for fake2
1282     # We set therefore limit to 3 sent messages: we expect 2 sent, 4 deferred (so 6 processed)
1283     t::lib::Mocks::mock_config( 'message_domain_limits', { domain => [
1284         { name => 'wrong.net',    limit => 6, unit => '1m' },
1285         { name => 'fake1.domain', limit => 1, unit => '1m' },
1286         { name => 'fake2.domain', limit => 0, unit => '1m' },
1287     ]});
1288     C4::Letters::EnqueueLetter($my_message) for 1..2;
1289     $my_message->{to_address} = 'someone@fake1.domain';
1290     C4::Letters::EnqueueLetter($my_message) for 1..2;
1291     $my_message->{to_address} = 'another@fake2.domain';
1292     C4::Letters::EnqueueLetter($my_message) for 1..2;
1293     my $mocked_util = Test::MockModule->new('Koha::Notice::Util');
1294     my $count_exceeds_calls = 0;
1295     $mocked_util->mock( 'exceeds_limit', sub {
1296         $count_exceeds_calls++;
1297         $mocked_util->original('exceeds_limit')->(@_);
1298     });
1299     warnings_like {
1300         $messages_sent = C4::Letters::SendQueuedMessages({ limit => 3 }) }
1301         [ qr/wrong.net reached limit/, $regex, qr/fake1.domain reached limit/, $regex ],
1302         "SendQueuedMessages with limit 2 and domain limits";
1303     is( $messages_sent, 2, 'Only expecting 2 sent messages' );
1304     is(  Koha::Notice::Messages->search({ status => 'pending' })->count, 4, 'Still 4 pending' );
1305     is( $count_exceeds_calls, 6, 'We saw 6 messages while checking domain limits: so we deferred 4' );
1306 };
1307
1308 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1309
1310     plan tests => 8;
1311
1312     my $dbh = C4::Context->dbh;
1313
1314     my $borrowernumber = Koha::Patron->new({
1315         firstname    => 'Jane',
1316         surname      => 'Smith',
1317         categorycode => $patron_category,
1318         branchcode   => $library->{branchcode},
1319         dateofbirth  => $date,
1320         smsalertnumber => undef,
1321     })->store->borrowernumber;
1322
1323     $dbh->do(q|DELETE FROM message_queue|);
1324     $my_message = {
1325         'letter' => {
1326             'content'      => 'a message',
1327             'metadata'     => 'metadata',
1328             'code'         => 'TEST_MESSAGE',
1329             'content_type' => 'text/plain',
1330             'title'        => 'message title'
1331         },
1332         'borrowernumber'         => $borrowernumber,
1333         'to_address'             => 'to@example.org',
1334         'message_transport_type' => 'email',
1335         'from_address'           => '@example.com' # invalid from_address
1336     };
1337     my $message_id = C4::Letters::EnqueueLetter($my_message);
1338     $send_or_die_count = 0; # reset
1339     my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1340     is( $send_or_die_count, 0, 'Nothing sent when one message_id passed' );
1341     my $message_1 = C4::Letters::GetMessage($message_id);
1342     is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1343     is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1344
1345     $my_message->{from_address} = 'root@example.org'; # valid from_address
1346     $message_id = C4::Letters::EnqueueLetter($my_message);
1347     warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1348         qr|Fake send_or_die|,
1349         "SendQueuedMessages is using the mocked send_or_die routine";
1350     is( $send_or_die_count, 1, 'One message passed through' );
1351     $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1352     my $message_2 = C4::Letters::GetMessage($message_id);
1353     is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1354     is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
1355 };