Bug 34014: Unit test
[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 => 93;
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 };
981
982 subtest 'get_item_content' => sub {
983     plan tests => 2;
984
985     t::lib::Mocks::mock_preference('dateformat', 'metric');
986     t::lib::Mocks::mock_preference('timeformat', '24hr');
987     my @items = (
988         {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
989         {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
990     );
991     my @item_content_fields = qw( date_due title barcode author itemnumber );
992
993     my $items_content;
994     for my $item ( @items ) {
995         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
996     }
997
998     my $expected_items_content = <<EOF;
999 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
1000 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
1001 EOF
1002     is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
1003
1004
1005     $items_content = q||;
1006     for my $item ( @items ) {
1007         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
1008     }
1009
1010     $expected_items_content = <<EOF;
1011 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
1012 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
1013 EOF
1014     is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
1015 };
1016
1017 subtest 'Test where parameter for SendQueuedMessages' => sub {
1018     plan tests => 3;
1019
1020     my $dbh = C4::Context->dbh;
1021
1022     my $borrowernumber = Koha::Patron->new({
1023         firstname    => 'Jane',
1024         surname      => 'Smith',
1025         categorycode => $patron_category,
1026         branchcode   => $library->{branchcode},
1027         dateofbirth  => $date,
1028     })->store->borrowernumber;
1029
1030     $dbh->do(q|DELETE FROM message_queue|);
1031     $my_message = {
1032         'letter' => {
1033             'content'      => 'a message',
1034             'metadata'     => 'metadata',
1035             'code'         => 'TEST_MESSAGE',
1036             'content_type' => 'text/plain',
1037             'title'        => 'message title'
1038         },
1039         'borrowernumber'         => $borrowernumber,
1040         'to_address'             => undef,
1041         'message_transport_type' => 'sms',
1042         'from_address'           => 'from@example.com'
1043     };
1044     my $my_message2 = {
1045         'letter' => {
1046             'content'      => 'another message',
1047             'metadata'     => 'metadata',
1048             'code'         => 'TEST_MESSAGE',
1049             'content_type' => 'text/plain',
1050             'title'        => 'message title'
1051         },
1052         'borrowernumber'         => $borrowernumber,
1053         'to_address'             => undef,
1054         'message_transport_type' => 'sms',
1055         'from_address'           => 'from@example.com'
1056     };
1057     my $my_message3 = {
1058         'letter' => {
1059             'content'      => 'a skipped message',
1060             'metadata'     => 'metadata',
1061             'code'         => 'TEST_MESSAGE',
1062             'content_type' => 'text/plain',
1063             'title'        => 'message title'
1064         },
1065         'borrowernumber'         => $borrowernumber,
1066         'to_address'             => undef,
1067         'message_transport_type' => 'sms',
1068         'from_address'           => 'from@example.com'
1069     };
1070     my @id = ( C4::Letters::EnqueueLetter($my_message),
1071         C4::Letters::EnqueueLetter($my_message2),
1072         C4::Letters::EnqueueLetter($my_message3),
1073     );
1074     C4::Letters::SendQueuedMessages({
1075         # Test scalar/arrayref in parameter too
1076         letter_code => [ 'TEST_MESSAGE', 'SOMETHING_NOT_THERE' ],
1077         type => 'sms',
1078         where => q{content NOT LIKE '%skipped%'},
1079     });
1080     is( Koha::Notice::Messages->find( $id[0] )->status, 'failed', 'Processed but failed' );
1081     is( Koha::Notice::Messages->find( $id[1] )->status, 'failed', 'Processed but failed' );
1082     is( Koha::Notice::Messages->find( $id[2] )->status, 'pending', 'Skipped, still pending' );
1083 };
1084
1085 subtest 'Test limit parameter for SendQueuedMessages' => sub {
1086     plan tests => 18;
1087
1088     my $dbh = C4::Context->dbh;
1089
1090     my $borrowernumber = Koha::Patron->new({
1091         firstname    => 'Jane',
1092         surname      => 'Smith',
1093         categorycode => $patron_category,
1094         branchcode   => $library->{branchcode},
1095         dateofbirth  => $date,
1096         email          => 'shouldnotwork@wrong.net',
1097     })->store->borrowernumber;
1098
1099     $dbh->do(q|DELETE FROM message_queue|);
1100     $my_message = {
1101         'letter' => {
1102             'content'      => 'a message',
1103             'metadata'     => 'metadata',
1104             'code'         => 'TEST_MESSAGE',
1105             'content_type' => 'text/plain',
1106             'title'        => 'message title'
1107         },
1108         'borrowernumber'         => $borrowernumber,
1109         'to_address'             => undef,
1110         'message_transport_type' => 'email',
1111         'from_address'           => 'from@example.com'
1112     };
1113     C4::Letters::EnqueueLetter($my_message) for 1..5;
1114
1115     $send_or_die_count = 0; # reset
1116     my $messages_sent;
1117     my $regex = qr|Fake send_or_die|;
1118     warning_like {
1119         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 1 } ) }
1120         $regex,
1121         "SendQueuedMessages with limit 1";
1122     is( $messages_sent, 1,
1123         'Sent 1 message with limit of 1 and 5 unprocessed messages' );
1124
1125     warnings_like {
1126         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 2 } ) }
1127         [ map { $regex } 1..2 ],
1128         "SendQueuedMessages with limit 2";
1129     is( $messages_sent, 2,
1130         'Sent 2 messages with limit of 2 and 4 unprocessed messages' );
1131
1132     warnings_like {
1133         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 3 } ) }
1134         [ map { $regex } 1..2 ],
1135         "SendQueuedMessages with limit 3";
1136     is( $messages_sent, 2,
1137         'Sent 2 messages with limit of 3 and 2 unprocessed messages' );
1138
1139     is( $send_or_die_count, 5, '5 messages sent' );
1140     # Mimic correct status in queue for next tests
1141     Koha::Notice::Messages->search({ to_address => { 'LIKE', '%wrong.net' }})->update({ status => 'sent' });
1142
1143     # Now add a few domain limits too, sending 2 more mails to wrongnet, 2 to fake1, 2 to fake2
1144     # Since we already sent 5 to wrong.net, we expect one deferral when limiting to 6
1145     # Similarly we arrange for 1 deferral for fake1, and 2 for fake2
1146     # We set therefore limit to 3 sent messages: we expect 2 sent, 4 deferred (so 6 processed)
1147     t::lib::Mocks::mock_config( 'message_domain_limits', { domain => [
1148         { name => 'wrong.net',    limit => 6, unit => '1m' },
1149         { name => 'fake1.domain', limit => 1, unit => '1m' },
1150         { name => 'fake2.domain', limit => 0, unit => '1m' },
1151     ]});
1152     C4::Letters::EnqueueLetter($my_message) for 1..2;
1153     $my_message->{to_address} = 'someone@fake1.domain';
1154     C4::Letters::EnqueueLetter($my_message) for 1..2;
1155     $my_message->{to_address} = 'another@fake2.domain';
1156     C4::Letters::EnqueueLetter($my_message) for 1..2;
1157     my $mocked_util = Test::MockModule->new('Koha::Notice::Util');
1158     my $count_exceeds_calls = 0;
1159     $mocked_util->mock( 'exceeds_limit', sub {
1160         $count_exceeds_calls++;
1161         $mocked_util->original('exceeds_limit')->(@_);
1162     });
1163     warnings_like {
1164         $messages_sent = C4::Letters::SendQueuedMessages({ limit => 3 }) }
1165         [ qr/wrong.net reached limit/, $regex, qr/fake1.domain reached limit/, $regex ],
1166         "SendQueuedMessages with limit 2 and domain limits";
1167     is( $messages_sent, 2, 'Only expecting 2 sent messages' );
1168     is(  Koha::Notice::Messages->search({ status => 'pending' })->count, 4, 'Still 4 pending' );
1169     is( $count_exceeds_calls, 6, 'We saw 6 messages while checking domain limits: so we deferred 4' );
1170 };
1171
1172 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1173
1174     plan tests => 8;
1175
1176     my $dbh = C4::Context->dbh;
1177
1178     my $borrowernumber = Koha::Patron->new({
1179         firstname    => 'Jane',
1180         surname      => 'Smith',
1181         categorycode => $patron_category,
1182         branchcode   => $library->{branchcode},
1183         dateofbirth  => $date,
1184         smsalertnumber => undef,
1185     })->store->borrowernumber;
1186
1187     $dbh->do(q|DELETE FROM message_queue|);
1188     $my_message = {
1189         'letter' => {
1190             'content'      => 'a message',
1191             'metadata'     => 'metadata',
1192             'code'         => 'TEST_MESSAGE',
1193             'content_type' => 'text/plain',
1194             'title'        => 'message title'
1195         },
1196         'borrowernumber'         => $borrowernumber,
1197         'to_address'             => 'to@example.org',
1198         'message_transport_type' => 'email',
1199         'from_address'           => '@example.com' # invalid from_address
1200     };
1201     my $message_id = C4::Letters::EnqueueLetter($my_message);
1202     $send_or_die_count = 0; # reset
1203     my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1204     is( $send_or_die_count, 0, 'Nothing sent when one message_id passed' );
1205     my $message_1 = C4::Letters::GetMessage($message_id);
1206     is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1207     is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1208
1209     $my_message->{from_address} = 'root@example.org'; # valid from_address
1210     $message_id = C4::Letters::EnqueueLetter($my_message);
1211     warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1212         qr|Fake send_or_die|,
1213         "SendQueuedMessages is using the mocked send_or_die routine";
1214     is( $send_or_die_count, 1, 'One message passed through' );
1215     $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1216     my $message_2 = C4::Letters::GetMessage($message_id);
1217     is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1218     is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
1219 };