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