Bug 33237: (QA follow-up) Clarify tests and Mock
[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 => 10;
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     # Note: What about includes, which language should be assumed 'default' here?
909
910     $letter = C4::Letters::GetPreparedLetter(
911             module                 => 'test',
912             tables                 => $tables,
913             letter_code            => 'code',
914             message_transport_type => 'email',
915             substitute             => $substitute,
916             lang                   => 'es-ES',
917     );
918     is( $letter->{title}, 'una prueba',
919         'GetPreparedLetter should return the required notice if it exists' );
920
921     $letter = C4::Letters::GetPreparedLetter(
922             module                 => 'test',
923             tables                 => $tables,
924             letter_code            => 'code',
925             message_transport_type => 'email',
926             substitute             => $substitute,
927             lang                   => 'fr-FR',
928     );
929     is(
930         $letter->{title},
931         'a test',
932         'GetPreparedLetter should return the default notice if the one required does not exist'
933     );
934
935     t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
936
937     $letter = C4::Letters::GetPreparedLetter(
938             module                 => 'test',
939             tables                 => $tables,
940             letter_code            => 'code',
941             message_transport_type => 'email',
942             substitute             => $substitute,
943             lang                   => 'es-ES',
944     );
945     is( $letter->{title}, 'a test',
946         'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
947
948     t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
949
950     my $amount      = -20;
951     my $accountline = $builder->build(
952         {
953             source => 'Accountline',
954             value  => {
955                 borrowernumber    => $borrowernumber,
956                 amount            => $amount,
957                 amountoutstanding => $amount,
958                 credit_type_code  => 'PAYMENT',
959             }
960         }
961     );
962     $dbh->do(
963         q|
964         INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
965         ('test', 'payment', '', 'Paiement du compte', 'Paiement du compte', "[% PROCESS 'accounts.inc' %][% PROCESS account_type_description account=credit %][% credit.description %]", 'print', 'fr-CA'),
966         ('test', 'payment', '', 'Default payment notice', 'Default payment notice', "[% PROCESS 'accounts.inc' %][% PROCESS account_type_description account=credit %][% credit.description %]", 'print', 'default');
967     |
968     );
969
970     t::lib::Mocks::mock_config( 'intrahtdocs', '/kohadevbox/koha/t/mock_templates/intranet-tmpl' );
971
972     $tables = {
973         borrowers => $borrowernumber,
974         credits   => $accountline->{accountlines_id},
975     };
976
977     $letter = C4::Letters::GetPreparedLetter(
978         module                 => 'test',
979         letter_code            => 'payment',
980         message_transport_type => 'print',
981         tables                 => $tables,
982         lang                   => 'fr-CA',
983     );
984     is( $letter->{title}, 'Paiement du compte',
985         'GetPreparedLetter should return the notice in patron\'s preferred language' );
986     like(
987         $letter->{content}, qr/Paiement/,
988         'Template includes should use the patron\'s preferred language too'
989     );
990
991     my $context = Test::MockModule->new('C4::Context');
992     $context->mock( 'interface', 'intranet' );
993
994     Koha::Cache::Memory::Lite->get_instance()->clear_from_cache('getlanguage');
995     t::lib::Mocks::mock_preference( 'language', 'fr-CA,en' );
996
997     $letter = C4::Letters::GetPreparedLetter(
998         module                 => 'test',
999         letter_code            => 'payment',
1000         message_transport_type => 'print',
1001         tables                 => $tables,
1002         lang                   => 'default',
1003     );
1004     is( $letter->{title}, 'Default payment notice',
1005         'GetPreparedLetter should return the notice in default language' );
1006     like( $letter->{content}, qr/Paiement/, 'GetPreparedLetter should return the notice in the interface language' );
1007
1008     $context->mock( 'interface', 'cron' );
1009
1010     $letter = C4::Letters::GetPreparedLetter(
1011         module                 => 'test',
1012         letter_code            => 'payment',
1013         message_transport_type => 'print',
1014         tables                 => $tables,
1015         lang                   => 'default'
1016     );
1017     is( $letter->{title}, 'Default payment notice',
1018         'GetPreparedLetter should return the notice in default language' );
1019     like(
1020         $letter->{content}, qr/Paiement/,
1021         'GetPreparedLetter should return the notice in the first language in language system preference'
1022     );
1023 };
1024
1025 subtest 'Test SMS handling in SendQueuedMessages' => sub {
1026
1027     plan tests => 14;
1028
1029     t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
1030     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
1031
1032     my $patron = Koha::Patrons->find($borrowernumber);
1033     $dbh->do(q|
1034         INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
1035         VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
1036         |, undef, $borrowernumber
1037     );
1038     eval { C4::Letters::SendQueuedMessages(); };
1039     is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
1040
1041     my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
1042     $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
1043     $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
1044
1045     warning_like { C4::Letters::SendQueuedMessages(); }
1046         qr|Fake send_or_die|,
1047         "SendAlerts is using the mocked send_or_die routine (claimissues)";
1048
1049     my $message = $schema->resultset('MessageQueue')->search({
1050         borrowernumber => $borrowernumber,
1051         status => 'sent'
1052     })->next();
1053
1054     is( $message->letter_id, $messages->[0]->{id}, "Message letter_id is set correctly" );
1055     is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
1056     is(
1057         $message->from_address(),
1058         'from@example.com',
1059         'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
1060     );
1061
1062     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1063
1064     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
1065
1066     $message_id = C4::Letters::EnqueueLetter($my_message);
1067     warning_like { C4::Letters::SendQueuedMessages(); }
1068         qr|Fake send_or_die|,
1069         "SendAlerts is using the mocked send_or_die routine (claimissues)";
1070
1071     $message = $schema->resultset('MessageQueue')->search({
1072         borrowernumber => $borrowernumber,
1073         status => 'sent'
1074     })->next();
1075
1076     is(
1077         $message->from_address(),
1078         'override@example.com',
1079         'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
1080     );
1081
1082     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
1083     $my_message->{to_address} = 'fixme@kidclamp.iswrong';
1084     $message_id = C4::Letters::EnqueueLetter($my_message);
1085
1086     my $number_attempted = C4::Letters::SendQueuedMessages({
1087         borrowernumber => -1, # -1 still triggers the borrowernumber condition
1088         letter_code    => 'PASSWORD_RESET',
1089     });
1090     is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
1091
1092     warning_like { C4::Letters::SendQueuedMessages(); }
1093         qr|Fake send_or_die|,
1094         "SendAlerts is using the mocked send_or_die routine (claimissues)";
1095
1096     my $sms_message_address = $schema->resultset('MessageQueue')->search({
1097         borrowernumber => $borrowernumber,
1098         status => 'sent'
1099     })->next()->to_address();
1100     is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
1101
1102     # Test using SMS::Send::Test driver that's bundled with SMS::Send
1103     t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
1104
1105     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1106     C4::Letters::EnqueueLetter($my_message);
1107     C4::Letters::SendQueuedMessages();
1108
1109     $sms_message_address = $schema->resultset('MessageQueue')->search({
1110         borrowernumber => $borrowernumber,
1111         status => 'sent'
1112     })->next()->to_address();
1113     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' );
1114
1115     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1116 };
1117
1118 subtest 'Test guarantor handling in SendQueuedMessages' => sub {
1119
1120     plan tests => 19;
1121
1122     t::lib::Mocks::mock_preference( 'borrowerRelationship', 'test' );
1123     t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 1 );
1124
1125     my $patron     = Koha::Patrons->find($borrowernumber);
1126
1127     my $patron_category =
1128         $builder->build( { source => 'Category', value => { category_type => 'A', can_be_guarantee => 0 } } )
1129         ->{categorycode};
1130     my $guarantor1 = $builder->build_object(
1131         { class => 'Koha::Patrons', value => { email => 'g1@email.com', categorycode => $patron_category } } );
1132     my $guarantor2 = $builder->build_object(
1133         { class => 'Koha::Patrons', value => { email => 'g2@email.com', categorycode => $patron_category } } );
1134
1135     $patron->add_guarantor( { guarantor_id => $guarantor1->borrowernumber, relationship => 'test' } );
1136     $patron->add_guarantor( { guarantor_id => $guarantor2->borrowernumber, relationship => 'test' } );
1137
1138     $my_message = {
1139         'letter' => {
1140             'content'      => 'a message',
1141             'metadata'     => 'metadata',
1142             'code'         => 'TEST_MESSAGE',
1143             'content_type' => 'text/plain',
1144             'title'        => 'message title'
1145         },
1146         'borrowernumber'         => $borrowernumber,
1147         'to_address'             => undef,
1148         'message_transport_type' => 'email',
1149         'from_address'           => 'from@example.com'
1150     };
1151     $message_id = C4::Letters::EnqueueLetter($my_message);
1152
1153     # feature disabled
1154     t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '0' );
1155
1156     warning_like { C4::Letters::SendQueuedMessages(); }
1157     qr|No 'to_address', email address or guarantors email address for borrowernumber|,
1158         "SendQueuedMessages fails when no to_address, patron notice email and RedirectGuaranteeEmail is not set";
1159
1160     # feature enabled
1161     t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '1' );
1162
1163     # reset message - testing without to or borrower valid email
1164     Koha::Notice::Messages->find($message_id)->delete;
1165     $message_id = C4::Letters::EnqueueLetter($my_message);
1166
1167     warning_like { C4::Letters::SendQueuedMessages(); }
1168     qr|Fake send_or_die|,
1169         "SendQueuedMessages is using the mocked send_or_die routine";
1170
1171     $message = $schema->resultset('MessageQueue')->search(
1172         {
1173             borrowernumber => $borrowernumber,
1174             status         => 'sent'
1175         }
1176     )->next();
1177
1178     is(
1179         $message->to_address(),
1180         $guarantor1->email,
1181         'SendQueuedMessages uses first guarantor email for "to" when patron has no email'
1182     );
1183
1184     is(
1185         $message->cc_address(),
1186         $guarantor2->email,
1187         'SendQueuedMessages sets cc address to second guarantor email when "to" takes first guarantor email'
1188     );
1189
1190     is( $email_object->email->header('To'), $guarantor1->email, "mailto correctly uses first guarantor" );
1191     is( $email_object->email->header('Cc'), $guarantor2->email, "cc correctly uses second guarantor" );
1192
1193     # reset message - testing borrower with valid email
1194     Koha::Notice::Messages->find($message_id)->delete;
1195     $message_id = C4::Letters::EnqueueLetter($my_message);
1196
1197     $patron->email('patron@example.com')->store();
1198
1199     warning_like { C4::Letters::SendQueuedMessages(); }
1200     qr|Fake send_or_die|,
1201         "SendQueuedMessages is using the mocked send_or_die routine";
1202
1203     $message = $schema->resultset('MessageQueue')->search(
1204         {
1205             borrowernumber => $borrowernumber,
1206             status         => 'sent'
1207         }
1208     )->next();
1209
1210     is(
1211         $message->to_address(),
1212         $patron->email,
1213         'SendQueuedMessages uses patron email when defined'
1214     );
1215
1216     is(
1217         $message->cc_address(),
1218         $guarantor1->email.",".$guarantor2->email,
1219         'SendQueuedMessages sets cc address to both guarantor emails when patron has email defined'
1220     );
1221
1222     is( $email_object->email->header('To'), $patron->email, "mailto correctly uses patrons email address" );
1223     is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1224
1225
1226     # reset message - testing explicit to passed to enqueue
1227     Koha::Notice::Messages->find($message_id)->delete;
1228     $my_message->{'to_address'} = 'to@example.com';
1229     $message_id = C4::Letters::EnqueueLetter($my_message);
1230
1231     warning_like { C4::Letters::SendQueuedMessages(); }
1232     qr|Fake send_or_die|,
1233         "SendQueuedMessages is using the mocked send_or_die routine";
1234
1235     $message = $schema->resultset('MessageQueue')->search(
1236         {
1237             borrowernumber => $borrowernumber,
1238             status         => 'sent'
1239         }
1240     )->next();
1241
1242     is(
1243         $message->to_address(),
1244         'to@example.com',
1245         'SendQueuedMessages uses to_address if it was specified at enqueue time'
1246     );
1247
1248     is(
1249         $message->cc_address(),
1250         $guarantor1->email.",".$guarantor2->email,
1251         'SendQueuedMessages sets cc address to both guarantor emails when "to" is already specified'
1252     );
1253
1254     is( $email_object->email->header('To'), 'to@example.com', "mailto correctly uses passed email" );
1255     is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1256
1257     # clear borrower queue
1258     Koha::Notice::Messages->find($message_id)->delete;
1259
1260     t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
1261 };
1262
1263 subtest 'get_item_content' => sub {
1264     plan tests => 2;
1265
1266     t::lib::Mocks::mock_preference('dateformat', 'metric');
1267     t::lib::Mocks::mock_preference('timeformat', '24hr');
1268     my @items = (
1269         {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
1270         {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
1271     );
1272     my @item_content_fields = qw( date_due title barcode author itemnumber );
1273
1274     my $items_content;
1275     for my $item ( @items ) {
1276         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
1277     }
1278
1279     my $expected_items_content = <<EOF;
1280 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
1281 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
1282 EOF
1283     is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
1284
1285
1286     $items_content = q||;
1287     for my $item ( @items ) {
1288         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
1289     }
1290
1291     $expected_items_content = <<EOF;
1292 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
1293 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
1294 EOF
1295     is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
1296 };
1297
1298 subtest 'Test where parameter for SendQueuedMessages' => sub {
1299     plan tests => 3;
1300
1301     my $dbh = C4::Context->dbh;
1302
1303     my $borrowernumber = Koha::Patron->new({
1304         firstname    => 'Jane',
1305         surname      => 'Smith',
1306         categorycode => $patron_category,
1307         branchcode   => $library->{branchcode},
1308         dateofbirth  => $date,
1309     })->store->borrowernumber;
1310
1311     $dbh->do(q|DELETE FROM message_queue|);
1312     $my_message = {
1313         'letter' => {
1314             'content'      => 'a message',
1315             'metadata'     => 'metadata',
1316             'code'         => 'TEST_MESSAGE',
1317             'content_type' => 'text/plain',
1318             'title'        => 'message title'
1319         },
1320         'borrowernumber'         => $borrowernumber,
1321         'to_address'             => undef,
1322         'message_transport_type' => 'sms',
1323         'from_address'           => 'from@example.com'
1324     };
1325     my $my_message2 = {
1326         'letter' => {
1327             'content'      => 'another message',
1328             'metadata'     => 'metadata',
1329             'code'         => 'TEST_MESSAGE',
1330             'content_type' => 'text/plain',
1331             'title'        => 'message title'
1332         },
1333         'borrowernumber'         => $borrowernumber,
1334         'to_address'             => undef,
1335         'message_transport_type' => 'sms',
1336         'from_address'           => 'from@example.com'
1337     };
1338     my $my_message3 = {
1339         'letter' => {
1340             'content'      => 'a skipped message',
1341             'metadata'     => 'metadata',
1342             'code'         => 'TEST_MESSAGE',
1343             'content_type' => 'text/plain',
1344             'title'        => 'message title'
1345         },
1346         'borrowernumber'         => $borrowernumber,
1347         'to_address'             => undef,
1348         'message_transport_type' => 'sms',
1349         'from_address'           => 'from@example.com'
1350     };
1351     my @id = ( C4::Letters::EnqueueLetter($my_message),
1352         C4::Letters::EnqueueLetter($my_message2),
1353         C4::Letters::EnqueueLetter($my_message3),
1354     );
1355     C4::Letters::SendQueuedMessages({
1356         # Test scalar/arrayref in parameter too
1357         letter_code => [ 'TEST_MESSAGE', 'SOMETHING_NOT_THERE' ],
1358         type => 'sms',
1359         where => q{content NOT LIKE '%skipped%'},
1360     });
1361     is( Koha::Notice::Messages->find( $id[0] )->status, 'failed', 'Processed but failed' );
1362     is( Koha::Notice::Messages->find( $id[1] )->status, 'failed', 'Processed but failed' );
1363     is( Koha::Notice::Messages->find( $id[2] )->status, 'pending', 'Skipped, still pending' );
1364 };
1365
1366 subtest 'Test limit parameter for SendQueuedMessages' => sub {
1367     plan tests => 18;
1368
1369     my $dbh = C4::Context->dbh;
1370
1371     my $borrowernumber = Koha::Patron->new({
1372         firstname    => 'Jane',
1373         surname      => 'Smith',
1374         categorycode => $patron_category,
1375         branchcode   => $library->{branchcode},
1376         dateofbirth  => $date,
1377         email          => 'shouldnotwork@wrong.net',
1378     })->store->borrowernumber;
1379
1380     $dbh->do(q|DELETE FROM message_queue|);
1381     $my_message = {
1382         'letter' => {
1383             'content'      => 'a message',
1384             'metadata'     => 'metadata',
1385             'code'         => 'TEST_MESSAGE',
1386             'content_type' => 'text/plain',
1387             'title'        => 'message title'
1388         },
1389         'borrowernumber'         => $borrowernumber,
1390         'to_address'             => undef,
1391         'message_transport_type' => 'email',
1392         'from_address'           => 'from@example.com'
1393     };
1394     C4::Letters::EnqueueLetter($my_message) for 1..5;
1395
1396     $send_or_die_count = 0; # reset
1397     my $messages_sent;
1398     my $regex = qr|Fake send_or_die|;
1399     warning_like {
1400         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 1 } ) }
1401         $regex,
1402         "SendQueuedMessages with limit 1";
1403     is( $messages_sent, 1,
1404         'Sent 1 message with limit of 1 and 5 unprocessed messages' );
1405
1406     warnings_like {
1407         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 2 } ) }
1408         [ map { $regex } 1..2 ],
1409         "SendQueuedMessages with limit 2";
1410     is( $messages_sent, 2,
1411         'Sent 2 messages with limit of 2 and 4 unprocessed messages' );
1412
1413     warnings_like {
1414         $messages_sent = C4::Letters::SendQueuedMessages( { limit => 3 } ) }
1415         [ map { $regex } 1..2 ],
1416         "SendQueuedMessages with limit 3";
1417     is( $messages_sent, 2,
1418         'Sent 2 messages with limit of 3 and 2 unprocessed messages' );
1419
1420     is( $send_or_die_count, 5, '5 messages sent' );
1421     # Mimic correct status in queue for next tests
1422     Koha::Notice::Messages->search({ to_address => { 'LIKE', '%wrong.net' }})->update({ status => 'sent' });
1423
1424     # Now add a few domain limits too, sending 2 more mails to wrongnet, 2 to fake1, 2 to fake2
1425     # Since we already sent 5 to wrong.net, we expect one deferral when limiting to 6
1426     # Similarly we arrange for 1 deferral for fake1, and 2 for fake2
1427     # We set therefore limit to 3 sent messages: we expect 2 sent, 4 deferred (so 6 processed)
1428     t::lib::Mocks::mock_config( 'message_domain_limits', { domain => [
1429         { name => 'wrong.net',    limit => 6, unit => '1m' },
1430         { name => 'fake1.domain', limit => 1, unit => '1m' },
1431         { name => 'fake2.domain', limit => 0, unit => '1m' },
1432     ]});
1433     C4::Letters::EnqueueLetter($my_message) for 1..2;
1434     $my_message->{to_address} = 'someone@fake1.domain';
1435     C4::Letters::EnqueueLetter($my_message) for 1..2;
1436     $my_message->{to_address} = 'another@fake2.domain';
1437     C4::Letters::EnqueueLetter($my_message) for 1..2;
1438     my $mocked_util = Test::MockModule->new('Koha::Notice::Util');
1439     my $count_exceeds_calls = 0;
1440     $mocked_util->mock( 'exceeds_limit', sub {
1441         $count_exceeds_calls++;
1442         $mocked_util->original('exceeds_limit')->(@_);
1443     });
1444     warnings_like {
1445         $messages_sent = C4::Letters::SendQueuedMessages({ limit => 3 }) }
1446         [ qr/wrong.net reached limit/, $regex, qr/fake1.domain reached limit/, $regex ],
1447         "SendQueuedMessages with limit 2 and domain limits";
1448     is( $messages_sent, 2, 'Only expecting 2 sent messages' );
1449     is(  Koha::Notice::Messages->search({ status => 'pending' })->count, 4, 'Still 4 pending' );
1450     is( $count_exceeds_calls, 6, 'We saw 6 messages while checking domain limits: so we deferred 4' );
1451 };
1452
1453 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1454
1455     plan tests => 8;
1456
1457     my $dbh = C4::Context->dbh;
1458
1459     my $borrowernumber = Koha::Patron->new({
1460         firstname    => 'Jane',
1461         surname      => 'Smith',
1462         categorycode => $patron_category,
1463         branchcode   => $library->{branchcode},
1464         dateofbirth  => $date,
1465         smsalertnumber => undef,
1466     })->store->borrowernumber;
1467
1468     $dbh->do(q|DELETE FROM message_queue|);
1469     $my_message = {
1470         'letter' => {
1471             'content'      => 'a message',
1472             'metadata'     => 'metadata',
1473             'code'         => 'TEST_MESSAGE',
1474             'content_type' => 'text/plain',
1475             'title'        => 'message title'
1476         },
1477         'borrowernumber'         => $borrowernumber,
1478         'to_address'             => 'to@example.org',
1479         'message_transport_type' => 'email',
1480         'from_address'           => '@example.com' # invalid from_address
1481     };
1482     my $message_id = C4::Letters::EnqueueLetter($my_message);
1483     $send_or_die_count = 0; # reset
1484     my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1485     is( $send_or_die_count, 0, 'Nothing sent when one message_id passed' );
1486     my $message_1 = C4::Letters::GetMessage($message_id);
1487     is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1488     is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1489
1490     $my_message->{from_address} = 'root@example.org'; # valid from_address
1491     $message_id = C4::Letters::EnqueueLetter($my_message);
1492     warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1493         qr|Fake send_or_die|,
1494         "SendQueuedMessages is using the mocked send_or_die routine";
1495     is( $send_or_die_count, 1, 'One message passed through' );
1496     $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1497     my $message_2 = C4::Letters::GetMessage($message_id);
1498     is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1499     is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
1500 };
1501
1502 subtest 'Template toolkit syntax in parameters' => sub {
1503
1504     my $borrowernumber = Koha::Patron->new(
1505         {
1506             firstname      => 'Robert',
1507             surname        => '[% USE Categories %][% Categories.all().search_related("borrowers").count() %]',
1508             categorycode   => $patron_category,
1509             branchcode     => $library->{branchcode},
1510             dateofbirth    => $date,
1511             smsalertnumber => undef,
1512         }
1513     )->store->borrowernumber;
1514
1515     my $title   = q|<<branches.branchname>> - <<status>>|;
1516     my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>};
1517
1518     $dbh->do(
1519         q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES (?,'my module','tt test','my name',1,?,?,'email')|,
1520         undef, $library->{branchcode}, $title, $content
1521     );
1522
1523     my $tables = {
1524         borrowers => $borrowernumber,
1525         branches  => $library->{branchcode},
1526         biblio    => $biblionumber,
1527     };
1528     my $substitute = {
1529         status => 'overdue',
1530     };
1531     my $prepared_letter = GetPreparedLetter(
1532         module      => 'my module',
1533         branchcode  => $library->{branchcode},
1534         letter_code => 'tt test',
1535         tables      => $tables,
1536         substitute  => $substitute,
1537         repeat      => [],
1538     );
1539
1540     is(
1541         $prepared_letter->{content},
1542         'Dear Robert [% USE Categories %][% Categories.all().search_related("borrowers").count() %]',
1543         'Template toolkit syntax in parameter was not evaluated.'
1544     );
1545 };