3 # This file is part of Koha.
5 # Copyright (C) 2013 Equinox Software, Inc.
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.
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.
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>.
21 use Test::More tests => 100;
26 use Email::Sender::Failure;
32 my ( $email_object, $sendmail_params );
33 our $send_or_die_count = 0;
35 my $email_sender_module = Test::MockModule->new('Email::Stuffer');
36 $email_sender_module->mock(
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";
43 warn "Fake send_or_die";
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 ));
53 use t::lib::TestBuilder;
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;
60 use Koha::Notice::Messages;
61 use Koha::Notice::Templates;
62 use Koha::Notice::Util;
64 use Koha::Subscriptions;
66 my $schema = Koha::Database->schema;
67 $schema->storage->txn_begin();
69 my $builder = t::lib::TestBuilder->new;
70 my $dbh = C4::Context->dbh;
72 $dbh->do(q|DELETE FROM letter|);
73 $dbh->do(q|DELETE FROM message_queue|);
74 $dbh->do(q|DELETE FROM message_transport_types|);
76 my $library = $builder->build({
79 branchemail => 'branchemail@address.com',
80 branchreplyto => 'branchreplyto@address.com',
81 branchreturnpath => 'branchreturnpath@address.com',
84 my $patron_category = $builder->build({ source => 'Category' })->{categorycode};
85 my $date = dt_from_string;
86 my $borrowernumber = Koha::Patron->new({
89 categorycode => $patron_category,
90 branchcode => $library->{branchcode},
92 smsalertnumber => undef,
93 })->store->borrowernumber;
95 my $marc_record = MARC::Record->new;
96 my( $biblionumber, $biblioitemnumber ) = AddBiblio( $marc_record, '' );
100 # GetMessageTransportTypes
101 my $mtts = C4::Letters::GetMessageTransportTypes();
102 is( @$mtts, 0, 'GetMessageTransportTypes returns the correct number of message types' );
105 INSERT INTO message_transport_types( message_transport_type ) VALUES ('email'), ('phone'), ('print'), ('sms')
107 $mtts = C4::Letters::GetMessageTransportTypes();
108 is_deeply( $mtts, ['email', 'phone', 'print', 'sms'], 'GetMessageTransportTypes returns all values' );
112 is( C4::Letters::EnqueueLetter(), undef, 'EnqueueLetter without argument returns undef' );
115 borrowernumber => $borrowernumber,
116 message_transport_type => 'sms',
118 from_address => 'from@example.com',
120 my $message_id = C4::Letters::EnqueueLetter($my_message);
121 is( $message_id, undef, 'EnqueueLetter without the letter argument returns undef' );
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',
132 $message_id = C4::Letters::EnqueueLetter($my_message);
133 is( $message_id, undef, 'EnqueueLetter without the message type argument argument returns undef' );
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');
141 my $messages = C4::Letters::GetQueuedMessages();
142 is( @$messages, 1, 'GetQueuedMessages without argument returns all the entries' );
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');
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;
163 C4::Letters::SendQueuedMessages( { message_id => undef } );
165 'Koha::Exceptions::BadParameter', 'Undef message_id throws an exception';
168 C4::Letters::SendQueuedMessages( { message_id => 0 } );
170 'Koha::Exceptions::BadParameter', 'message_id of 0 throws an exception';
173 C4::Letters::SendQueuedMessages( { message_id => q{} } );
175 'Koha::Exceptions::BadParameter', 'Empty string message_id throws an exception';
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 });
183 $messages->[0]->{status},
185 'message marked failed if tried to send SMS message for borrower with no smsalertnumber set (bug 11208)'
188 $messages->[0]->{failure_code},
190 'Correct failure code set for borrower with no smsalertnumber set'
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' );
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' );
206 is( $messages->[0]->{failure_code}, 'MISSING_SMS', 'Failure code set correctly for no smsalertnumber correctly set' );
209 my $letters = C4::Letters::GetLetters();
210 is( @$letters, 0, 'GetLetters returns the correct number of letters' );
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.
216 <<branches.branchname>>
217 <<branches.branchaddress1>>
220 The following item(s) is/are currently <<status>>:
222 <item> <<count>>. <<items.itemcallnumber>>, Barcode: <<items.barcode>> </item>
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>>.
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' );
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 } );
241 $letters = GetLetters( { module => 'blah' } );
242 is( scalar(@$letters), 2, 'GetLetters returns the 2 inserted letters' );
244 my ($ISBN_letter) = grep { $_->{code} eq 'ISBN' } @$letters;
245 is( $ISBN_letter->{name}, 'book', 'letter name for "ISBN" letter is book' );
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{} );
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 );
256 borrowers => $borrowernumber,
257 branches => $library->{branchcode},
258 biblio => $biblionumber,
265 itemcallnumber => 'my callnumber1',
269 itemcallnumber => 'my callnumber2',
273 my $prepared_letter = GetPreparedLetter((
274 module => 'my module',
275 branchcode => $library->{branchcode},
276 letter_code => 'my code',
278 substitute => $substitute,
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.
287 |.$retrieved_library->branchname.qq|
288 |.$retrieved_library->branchaddress1.qq|
289 URL: http://thisisatest.com
291 The following item(s) is/are currently $substitute->{status}:
293 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
294 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
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";
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' );
303 $prepared_letter = GetPreparedLetter((
304 module => 'my module',
305 branchcode => $library->{branchcode},
306 letter_code => 'my code',
308 substitute => $substitute,
310 message_transport_type => 'sms',
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' );
316 $prepared_letter = GetPreparedLetter((
317 module => 'my module',
318 branchcode => $library->{branchcode},
319 letter_code => 'my code',
321 substitute => { status => undef },
323 message_transport_type => 'sms',
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' );
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',
333 letter_code => 'test_date',
335 substitute => $substitute,
338 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
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',
344 letter_code => 'test_date',
346 substitute => $substitute,
349 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
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',
355 letter_code => 'test_date',
357 substitute => $substitute,
360 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
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',
369 letter_code => 'test_date',
371 substitute => $substitute,
374 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
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 %]');});
379 my $testacqorder2_content = <<EOF;
381 [% bookseller.name %]
382 [% FOREACH order IN orders %]
383 Ordernumber [% order.ordernumber %] [% order.quantity %] [% order.listprice | \$Price %]
387 $dbh->do("INSERT INTO letter (module, code, name, title, content) VALUES ('orderacquisition','TESTACQORDER2','Acquisition Order','Order','$testacqorder2_content');");
389 my $popito = $builder->build({
390 source => 'Aqbookseller',
391 value => { name => 'Popito' }
394 my $order_1 = $builder->build({
402 my $order_2 = $builder->build({
410 $prepared_letter = GetPreparedLetter((
411 module => 'orderacquisition',
413 letter_code => 'TESTACQORDER2',
414 tables => { 'aqbooksellers' => $popito->{id} },
416 aqorders => [ $order_1->{ordernumber}, $order_2->{ordernumber} ]
420 my $testacqorder2_expected = qq|Popito
422 Ordernumber | . $order_1->{ordernumber} . qq| 2 12.00
424 Ordernumber | . $order_2->{ordernumber} . qq| 1 23.50
428 is($prepared_letter->{content}, $testacqorder2_expected);
430 # Test that _parseletter doesn't modify its parameters bug 15429
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" );
437 # Correctly format dateexpiry
439 my $values = { dateexpiry => '2015-12-13', };
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' );
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' );
452 my $bookseller = Koha::Acquisition::Bookseller->new(
455 address1 => "bookseller's address",
461 my $booksellerid = $bookseller->id;
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');
467 my $budgetid = C4::Budgets::AddBudget({
468 budget_code => "budget_code_test_letters",
469 budget_name => "budget_name_test_letters",
472 my $bib = MARC::Record->new();
473 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
475 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
479 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
483 my $logged_in_user = $builder->build_object(
485 class => 'Koha::Patrons',
487 branchcode => $library->{branchcode},
488 email => 'some@email.com'
493 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
495 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
496 my $order = Koha::Acquisition::Order->new(
498 basketno => $basketno,
500 biblionumber => $biblionumber,
501 budget_id => $budgetid,
504 my $ordernumber = $order->ordernumber;
506 Koha::Acquisition::Baskets->find( $basketno )->close;
509 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
510 qr/^Bookseller .* without emails at/,
511 "SendAlerts prints a warning";
512 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
514 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
515 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
517 # Ensure that the preference 'ClaimsLog' is set to logging
518 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
520 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
521 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
525 $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' )
527 qr|Fake send_or_die|,
528 "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
529 is( $err, 1, "Successfully sent order." );
530 is( $email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent order" );
531 is( scalar $email_object->email->header('Reply-To'), undef, "No reply-to address is set" );
533 $email_object->email->body,
534 'my vendor|John Smith|Ordernumber '
536 . ' (Silence in the library) (1 ordered) Basket name: The basket name',
537 'Order notice text constructed successfully'
540 # SendAlerts should use specific email addresses if set
541 t::lib::Mocks::mock_preference( 'AcquisitionsDefaultEmailAddress', 'acq-default@domain.com' );
542 t::lib::Mocks::mock_preference( 'AcquisitionsDefaultReplyTo', 'acq-replyto@domain.com' );
545 $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' )
547 qr|Fake send_or_die|,
548 "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
550 $email_object->email->header('From'), 'acq-default@domain.com',
551 "AcquisitionsDefaultEmailAddress is used to sent acq notification"
554 $email_object->email->header('Reply-To'), 'acq-replyto@domain.com',
555 "AcquisitionsDefaultReplyTo is used to sent acq notification"
558 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
559 $mocked_koha_email->mock(
562 Email::Sender::Failure->throw('something went wrong');
567 $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' );
569 qr{something went wrong},
570 'Warning is printed';
572 is( $err->{error}, 'something went wrong', "Send exception, error message returned" );
574 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
576 $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' )
578 qr/No orderacquisition TESTACQORDER letter transported by email/,
579 "GetPreparedLetter warns about missing notice template";
580 is( $err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined." );
585 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
586 qr|Fake send_or_die|,
587 "SendAlerts is using the mocked send_or_die routine";
589 is($err, 1, "Successfully sent claim");
590 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
591 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
593 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
594 $mocked_koha_email->mock( 'send_or_die', sub {
595 Email::Sender::Failure->throw('something went wrong');
599 $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
600 qr{something went wrong},
601 'Warning is printed';
603 is($err->{error}, 'something went wrong', "Send exception, error message returned");
607 use C4::Serials qw( NewSubscription GetSerials findSerialsByStatus ModSerialStatus );
610 my $internalnotes = 'intnotes';
611 my $number_pattern = $builder->build_object(
613 class => 'Koha::Subscription::Numberpatterns',
614 value => { numberingmethod => 'No. {X}' }
617 my $subscriptionid = NewSubscription(
618 undef, "", undef, undef, undef, $biblionumber,
619 '2013-01-01', 1, undef, undef, undef,
620 undef, undef, undef, undef, undef, undef,
621 1, $notes,undef, '2013-01-01', undef, $number_pattern->id,
622 undef, undef, 0, $internalnotes, 0,
623 undef, undef, 0, undef, '2013-12-31', 0
625 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('serial','RLIST','Serial issue notification','Serial issue notification','<<biblio.title>>,<<subscription.subscriptionid>>,<<serial.serialseq>>');});
626 my ($serials_count, @serials) = GetSerials($subscriptionid);
627 my $serial = $serials[0];
629 my $patron = Koha::Patron->new({
632 categorycode => $patron_category,
633 branchcode => $library->{branchcode},
634 dateofbirth => $date,
635 email => 'john.smith@test.de',
637 my $borrowernumber = $patron->borrowernumber;
638 my $subscription = Koha::Subscriptions->find( $subscriptionid );
639 $subscription->add_subscriber( $patron );
641 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
644 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
645 qr|Fake send_or_die|,
646 "SendAlerts is using the mocked send_or_die routine";
648 is($err2, 1, "Successfully sent serial notification");
649 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
650 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
652 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
656 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
657 qr|Fake send_or_die|,
658 "SendAlerts is using the mocked send_or_die routine";
659 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
661 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
662 $mocked_koha_email->mock( 'send_or_die', sub {
663 Email::Sender::Failure->throw('something went wrong');
667 $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
668 qr{something went wrong},
669 'Warning is printed';
671 is($err->{error}, 'something went wrong', "Send exception, error message returned");
674 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
676 subtest '_parseletter' => sub {
679 # Regression test for bug 10843
680 # $dt->add takes a scalar, not undef
682 t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay', undef );
683 $letter = C4::Letters::_parseletter( undef, 'reserves', { waitingdate => "2013-01-01" } );
684 is( ref($letter), 'HASH' );
685 t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay', 1 );
686 $letter = C4::Letters::_parseletter( undef, 'reserves', { waitingdate => "2013-01-01" } );
687 is( ref($letter), 'HASH' );
690 subtest 'SendAlerts - claimissue' => sub {
695 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('claimissues','TESTSERIALCLAIM','Serial claim test','Serial claim test','<<serial.serialid>>|<<subscription.startdate>>|<<biblio.title>>|<<biblioitems.issn>>');});
697 my $bookseller = Koha::Acquisition::Bookseller->new(
700 address1 => "bookseller's address",
706 my $booksellerid = $bookseller->id;
708 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
710 my $bib = MARC::Record->new();
711 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
713 MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
714 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
718 MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
719 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
722 my ($biblionumber) = AddBiblio($bib, '');
724 my $number_pattern = $builder->build_object(
726 class => 'Koha::Subscription::Numberpatterns',
727 value => { numberingmethod => 'No. {X}' }
731 my $subscriptionid = NewSubscription(
732 undef, "", $booksellerid, undef, undef, $biblionumber,
733 '2013-01-01', 1, undef, undef, undef,
734 undef, undef, undef, undef, undef, undef,
735 1, 'public',undef, '2013-01-01', undef, $number_pattern->id,
736 undef, undef, 0, 'internal', 0,
737 undef, undef, 0, undef, '2013-12-31', 0
740 my ($serials_count, @serials) = GetSerials($subscriptionid);
741 my @serialids = ($serials[0]->{serialid});
745 $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
746 qr/^Bookseller .* without emails at/,
747 "Warn on vendor without email address";
749 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
750 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
752 # Ensure that the preference 'ClaimsLog' is set to logging
753 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
755 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
756 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
758 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
762 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
763 qr|Fake send_or_die|,
764 "SendAlerts is using the mocked send_or_die routine (claimissues)";
765 is( $err, 1, "Successfully sent claim" );
766 is( $email_object->email->header('To'),
767 'testemail@mydomain.com', "mailto correct in sent claim" );
768 is( scalar $email_object->email->header('Reply-To'), undef, "reply-to is not set" );
770 $email_object->email->body,
771 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
772 'Serial claim letter for 1 issue constructed successfully'
776 t::lib::Mocks::mock_preference( 'SerialsDefaultEmailAddress', 'ser-default@domain.com' );
777 t::lib::Mocks::mock_preference( 'SerialsDefaultReplyTo', 'ser-replyto@domain.com' );
781 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
782 qr|Fake send_or_die|,
783 "SendAlerts is using the mocked send_or_die routine (claimissues)";
784 is( $email_object->email->header('From'),
785 'ser-default@domain.com', "SerialsDefaultEmailAddress is used to serial claim issue" );
786 is( $email_object->email->header('Reply-To'),
787 'ser-replyto@domain.com', "SerialsDefaultReplyTo is used to sent serial claim issue" );
789 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
790 $mocked_koha_email->mock( 'send_or_die', sub {
791 Email::Sender::Failure->throw('something went wrong');
795 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
796 qr{something went wrong},
797 'Warning is printed';
799 is($err->{error}, 'something went wrong', "Send exception, error message returned");
803 my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
804 my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
805 ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
806 ($serials_count, @serials) = GetSerials($subscriptionid);
807 push @serialids, ($serials[1]->{serialid});
809 warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
810 qr|Fake send_or_die|,
811 "SendAlerts is using the mocked send_or_die routine (claimissues)";
814 $email_object->email->body,
815 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
816 . $email_object->email->crlf
817 . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
818 "Serial claim letter for 2 issues constructed successfully"
821 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
823 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
824 qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
825 "GetPreparedLetter warns about missing notice template";
826 is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
831 subtest 'GetPreparedLetter' => sub {
834 Koha::Notice::Template->new(
839 message_transport_type => 'email'
844 $letter = C4::Letters::GetPreparedLetter(
846 letter_code => 'test',
849 qr{^ERROR: nothing to substitute},
850 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
852 'No letter should be returned by GetPreparedLetter if something went wrong'
856 $letter = C4::Letters::GetPreparedLetter(
858 letter_code => 'test',
862 qr{^ERROR: nothing to substitute},
863 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
865 'No letter should be returned by GetPreparedLetter if something went wrong'
872 subtest 'TranslateNotices' => sub {
875 t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
879 INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
880 ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
881 ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
884 my $letter = C4::Letters::GetPreparedLetter(
887 letter_code => 'code',
888 message_transport_type => 'email',
889 substitute => $substitute,
894 'GetPreparedLetter should return the default one if the lang parameter is not provided'
897 $letter = C4::Letters::GetPreparedLetter(
900 letter_code => 'code',
901 message_transport_type => 'email',
902 substitute => $substitute,
905 is( $letter->{title}, 'una prueba',
906 'GetPreparedLetter should return the required notice if it exists' );
908 $letter = C4::Letters::GetPreparedLetter(
911 letter_code => 'code',
912 message_transport_type => 'email',
913 substitute => $substitute,
919 'GetPreparedLetter should return the default notice if the one required does not exist'
922 t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
924 $letter = C4::Letters::GetPreparedLetter(
927 letter_code => 'code',
928 message_transport_type => 'email',
929 substitute => $substitute,
932 is( $letter->{title}, 'a test',
933 'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
937 subtest 'Test SMS handling in SendQueuedMessages' => sub {
941 t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
942 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
944 my $patron = Koha::Patrons->find($borrowernumber);
946 INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
947 VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
948 |, undef, $borrowernumber
950 eval { C4::Letters::SendQueuedMessages(); };
951 is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
953 my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
954 $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
955 $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
957 warning_like { C4::Letters::SendQueuedMessages(); }
958 qr|Fake send_or_die|,
959 "SendAlerts is using the mocked send_or_die routine (claimissues)";
961 my $message = $schema->resultset('MessageQueue')->search({
962 borrowernumber => $borrowernumber,
966 is( $message->letter_id, $messages->[0]->{id}, "Message letter_id is set correctly" );
967 is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
969 $message->from_address(),
971 'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
974 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
976 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
978 $message_id = C4::Letters::EnqueueLetter($my_message);
979 warning_like { C4::Letters::SendQueuedMessages(); }
980 qr|Fake send_or_die|,
981 "SendAlerts is using the mocked send_or_die routine (claimissues)";
983 $message = $schema->resultset('MessageQueue')->search({
984 borrowernumber => $borrowernumber,
989 $message->from_address(),
990 'override@example.com',
991 'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
994 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
995 $my_message->{to_address} = 'fixme@kidclamp.iswrong';
996 $message_id = C4::Letters::EnqueueLetter($my_message);
998 my $number_attempted = C4::Letters::SendQueuedMessages({
999 borrowernumber => -1, # -1 still triggers the borrowernumber condition
1000 letter_code => 'PASSWORD_RESET',
1002 is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
1004 warning_like { C4::Letters::SendQueuedMessages(); }
1005 qr|Fake send_or_die|,
1006 "SendAlerts is using the mocked send_or_die routine (claimissues)";
1008 my $sms_message_address = $schema->resultset('MessageQueue')->search({
1009 borrowernumber => $borrowernumber,
1011 })->next()->to_address();
1012 is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
1014 # Test using SMS::Send::Test driver that's bundled with SMS::Send
1015 t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
1017 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1018 C4::Letters::EnqueueLetter($my_message);
1019 C4::Letters::SendQueuedMessages();
1021 $sms_message_address = $schema->resultset('MessageQueue')->search({
1022 borrowernumber => $borrowernumber,
1024 })->next()->to_address();
1025 is( $sms_message_address, '5555555555', 'SendQueuedMessages populates the to address correctly for SMS by SMS::Send driver to smsalertnumber when to_address is set incorrectly' );
1027 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1030 subtest 'Test guarantor handling in SendQueuedMessages' => sub {
1034 t::lib::Mocks::mock_preference( 'borrowerRelationship', 'test' );
1035 t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 1 );
1037 my $patron = Koha::Patrons->find($borrowernumber);
1039 my $patron_category =
1040 $builder->build( { source => 'Category', value => { category_type => 'A', can_be_guarantee => 0 } } )
1042 my $guarantor1 = $builder->build_object(
1043 { class => 'Koha::Patrons', value => { email => 'g1@email.com', categorycode => $patron_category } } );
1044 my $guarantor2 = $builder->build_object(
1045 { class => 'Koha::Patrons', value => { email => 'g2@email.com', categorycode => $patron_category } } );
1047 $patron->add_guarantor( { guarantor_id => $guarantor1->borrowernumber, relationship => 'test' } );
1048 $patron->add_guarantor( { guarantor_id => $guarantor2->borrowernumber, relationship => 'test' } );
1052 'content' => 'a message',
1053 'metadata' => 'metadata',
1054 'code' => 'TEST_MESSAGE',
1055 'content_type' => 'text/plain',
1056 'title' => 'message title'
1058 'borrowernumber' => $borrowernumber,
1059 'to_address' => undef,
1060 'message_transport_type' => 'email',
1061 'from_address' => 'from@example.com'
1063 $message_id = C4::Letters::EnqueueLetter($my_message);
1066 t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '0' );
1068 warning_like { C4::Letters::SendQueuedMessages(); }
1069 qr|No 'to_address', email address or guarantors email address for borrowernumber|,
1070 "SendQueuedMessages fails when no to_address, patron notice email and RedirectGuaranteeEmail is not set";
1073 t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '1' );
1075 # reset message - testing without to or borrower valid email
1076 Koha::Notice::Messages->find($message_id)->delete;
1077 $message_id = C4::Letters::EnqueueLetter($my_message);
1079 warning_like { C4::Letters::SendQueuedMessages(); }
1080 qr|Fake send_or_die|,
1081 "SendQueuedMessages is using the mocked send_or_die routine";
1083 $message = $schema->resultset('MessageQueue')->search(
1085 borrowernumber => $borrowernumber,
1091 $message->to_address(),
1093 'SendQueuedMessages uses first guarantor email for "to" when patron has no email'
1097 $message->cc_address(),
1099 'SendQueuedMessages sets cc address to second guarantor email when "to" takes first guarantor email'
1102 is( $email_object->email->header('To'), $guarantor1->email, "mailto correctly uses first guarantor" );
1103 is( $email_object->email->header('Cc'), $guarantor2->email, "cc correctly uses second guarantor" );
1105 # reset message - testing borrower with valid email
1106 Koha::Notice::Messages->find($message_id)->delete;
1107 $message_id = C4::Letters::EnqueueLetter($my_message);
1109 $patron->email('patron@example.com')->store();
1111 warning_like { C4::Letters::SendQueuedMessages(); }
1112 qr|Fake send_or_die|,
1113 "SendQueuedMessages is using the mocked send_or_die routine";
1115 $message = $schema->resultset('MessageQueue')->search(
1117 borrowernumber => $borrowernumber,
1123 $message->to_address(),
1125 'SendQueuedMessages uses patron email when defined'
1129 $message->cc_address(),
1130 $guarantor1->email.",".$guarantor2->email,
1131 'SendQueuedMessages sets cc address to both guarantor emails when patron has email defined'
1134 is( $email_object->email->header('To'), $patron->email, "mailto correctly uses patrons email address" );
1135 is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1138 # reset message - testing explicit to passed to enqueue
1139 Koha::Notice::Messages->find($message_id)->delete;
1140 $my_message->{'to_address'} = 'to@example.com';
1141 $message_id = C4::Letters::EnqueueLetter($my_message);
1143 warning_like { C4::Letters::SendQueuedMessages(); }
1144 qr|Fake send_or_die|,
1145 "SendQueuedMessages is using the mocked send_or_die routine";
1147 $message = $schema->resultset('MessageQueue')->search(
1149 borrowernumber => $borrowernumber,
1155 $message->to_address(),
1157 'SendQueuedMessages uses to_address if it was specified at enqueue time'
1161 $message->cc_address(),
1162 $guarantor1->email.",".$guarantor2->email,
1163 'SendQueuedMessages sets cc address to both guarantor emails when "to" is already specified'
1166 is( $email_object->email->header('To'), 'to@example.com', "mailto correctly uses passed email" );
1167 is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1169 # clear borrower queue
1170 Koha::Notice::Messages->find($message_id)->delete;
1172 t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
1175 subtest 'get_item_content' => sub {
1178 t::lib::Mocks::mock_preference('dateformat', 'metric');
1179 t::lib::Mocks::mock_preference('timeformat', '24hr');
1181 {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
1182 {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
1184 my @item_content_fields = qw( date_due title barcode author itemnumber );
1187 for my $item ( @items ) {
1188 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
1191 my $expected_items_content = <<EOF;
1192 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
1193 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
1195 is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
1198 $items_content = q||;
1199 for my $item ( @items ) {
1200 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
1203 $expected_items_content = <<EOF;
1204 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
1205 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
1207 is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
1210 subtest 'Test where parameter for SendQueuedMessages' => sub {
1213 my $dbh = C4::Context->dbh;
1215 my $borrowernumber = Koha::Patron->new({
1216 firstname => 'Jane',
1218 categorycode => $patron_category,
1219 branchcode => $library->{branchcode},
1220 dateofbirth => $date,
1221 })->store->borrowernumber;
1223 $dbh->do(q|DELETE FROM message_queue|);
1226 'content' => 'a message',
1227 'metadata' => 'metadata',
1228 'code' => 'TEST_MESSAGE',
1229 'content_type' => 'text/plain',
1230 'title' => 'message title'
1232 'borrowernumber' => $borrowernumber,
1233 'to_address' => undef,
1234 'message_transport_type' => 'sms',
1235 'from_address' => 'from@example.com'
1239 'content' => 'another message',
1240 'metadata' => 'metadata',
1241 'code' => 'TEST_MESSAGE',
1242 'content_type' => 'text/plain',
1243 'title' => 'message title'
1245 'borrowernumber' => $borrowernumber,
1246 'to_address' => undef,
1247 'message_transport_type' => 'sms',
1248 'from_address' => 'from@example.com'
1252 'content' => 'a skipped message',
1253 'metadata' => 'metadata',
1254 'code' => 'TEST_MESSAGE',
1255 'content_type' => 'text/plain',
1256 'title' => 'message title'
1258 'borrowernumber' => $borrowernumber,
1259 'to_address' => undef,
1260 'message_transport_type' => 'sms',
1261 'from_address' => 'from@example.com'
1263 my @id = ( C4::Letters::EnqueueLetter($my_message),
1264 C4::Letters::EnqueueLetter($my_message2),
1265 C4::Letters::EnqueueLetter($my_message3),
1267 C4::Letters::SendQueuedMessages({
1268 # Test scalar/arrayref in parameter too
1269 letter_code => [ 'TEST_MESSAGE', 'SOMETHING_NOT_THERE' ],
1271 where => q{content NOT LIKE '%skipped%'},
1273 is( Koha::Notice::Messages->find( $id[0] )->status, 'failed', 'Processed but failed' );
1274 is( Koha::Notice::Messages->find( $id[1] )->status, 'failed', 'Processed but failed' );
1275 is( Koha::Notice::Messages->find( $id[2] )->status, 'pending', 'Skipped, still pending' );
1278 subtest 'Test limit parameter for SendQueuedMessages' => sub {
1281 my $dbh = C4::Context->dbh;
1283 my $borrowernumber = Koha::Patron->new({
1284 firstname => 'Jane',
1286 categorycode => $patron_category,
1287 branchcode => $library->{branchcode},
1288 dateofbirth => $date,
1289 email => 'shouldnotwork@wrong.net',
1290 })->store->borrowernumber;
1292 $dbh->do(q|DELETE FROM message_queue|);
1295 'content' => 'a message',
1296 'metadata' => 'metadata',
1297 'code' => 'TEST_MESSAGE',
1298 'content_type' => 'text/plain',
1299 'title' => 'message title'
1301 'borrowernumber' => $borrowernumber,
1302 'to_address' => undef,
1303 'message_transport_type' => 'email',
1304 'from_address' => 'from@example.com'
1306 C4::Letters::EnqueueLetter($my_message) for 1..5;
1308 $send_or_die_count = 0; # reset
1310 my $regex = qr|Fake send_or_die|;
1312 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 1 } ) }
1314 "SendQueuedMessages with limit 1";
1315 is( $messages_sent, 1,
1316 'Sent 1 message with limit of 1 and 5 unprocessed messages' );
1319 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 2 } ) }
1320 [ map { $regex } 1..2 ],
1321 "SendQueuedMessages with limit 2";
1322 is( $messages_sent, 2,
1323 'Sent 2 messages with limit of 2 and 4 unprocessed messages' );
1326 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 3 } ) }
1327 [ map { $regex } 1..2 ],
1328 "SendQueuedMessages with limit 3";
1329 is( $messages_sent, 2,
1330 'Sent 2 messages with limit of 3 and 2 unprocessed messages' );
1332 is( $send_or_die_count, 5, '5 messages sent' );
1333 # Mimic correct status in queue for next tests
1334 Koha::Notice::Messages->search({ to_address => { 'LIKE', '%wrong.net' }})->update({ status => 'sent' });
1336 # Now add a few domain limits too, sending 2 more mails to wrongnet, 2 to fake1, 2 to fake2
1337 # Since we already sent 5 to wrong.net, we expect one deferral when limiting to 6
1338 # Similarly we arrange for 1 deferral for fake1, and 2 for fake2
1339 # We set therefore limit to 3 sent messages: we expect 2 sent, 4 deferred (so 6 processed)
1340 t::lib::Mocks::mock_config( 'message_domain_limits', { domain => [
1341 { name => 'wrong.net', limit => 6, unit => '1m' },
1342 { name => 'fake1.domain', limit => 1, unit => '1m' },
1343 { name => 'fake2.domain', limit => 0, unit => '1m' },
1345 C4::Letters::EnqueueLetter($my_message) for 1..2;
1346 $my_message->{to_address} = 'someone@fake1.domain';
1347 C4::Letters::EnqueueLetter($my_message) for 1..2;
1348 $my_message->{to_address} = 'another@fake2.domain';
1349 C4::Letters::EnqueueLetter($my_message) for 1..2;
1350 my $mocked_util = Test::MockModule->new('Koha::Notice::Util');
1351 my $count_exceeds_calls = 0;
1352 $mocked_util->mock( 'exceeds_limit', sub {
1353 $count_exceeds_calls++;
1354 $mocked_util->original('exceeds_limit')->(@_);
1357 $messages_sent = C4::Letters::SendQueuedMessages({ limit => 3 }) }
1358 [ qr/wrong.net reached limit/, $regex, qr/fake1.domain reached limit/, $regex ],
1359 "SendQueuedMessages with limit 2 and domain limits";
1360 is( $messages_sent, 2, 'Only expecting 2 sent messages' );
1361 is( Koha::Notice::Messages->search({ status => 'pending' })->count, 4, 'Still 4 pending' );
1362 is( $count_exceeds_calls, 6, 'We saw 6 messages while checking domain limits: so we deferred 4' );
1365 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1369 my $dbh = C4::Context->dbh;
1371 my $borrowernumber = Koha::Patron->new({
1372 firstname => 'Jane',
1374 categorycode => $patron_category,
1375 branchcode => $library->{branchcode},
1376 dateofbirth => $date,
1377 smsalertnumber => undef,
1378 })->store->borrowernumber;
1380 $dbh->do(q|DELETE FROM message_queue|);
1383 'content' => 'a message',
1384 'metadata' => 'metadata',
1385 'code' => 'TEST_MESSAGE',
1386 'content_type' => 'text/plain',
1387 'title' => 'message title'
1389 'borrowernumber' => $borrowernumber,
1390 'to_address' => 'to@example.org',
1391 'message_transport_type' => 'email',
1392 'from_address' => '@example.com' # invalid from_address
1394 my $message_id = C4::Letters::EnqueueLetter($my_message);
1395 $send_or_die_count = 0; # reset
1396 my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1397 is( $send_or_die_count, 0, 'Nothing sent when one message_id passed' );
1398 my $message_1 = C4::Letters::GetMessage($message_id);
1399 is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1400 is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1402 $my_message->{from_address} = 'root@example.org'; # valid from_address
1403 $message_id = C4::Letters::EnqueueLetter($my_message);
1404 warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1405 qr|Fake send_or_die|,
1406 "SendQueuedMessages is using the mocked send_or_die routine";
1407 is( $send_or_die_count, 1, 'One message passed through' );
1408 $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1409 my $message_2 = C4::Letters::GetMessage($message_id);
1410 is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1411 is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
1414 subtest 'Template toolkit syntax in parameters' => sub {
1416 my $borrowernumber = Koha::Patron->new(
1418 firstname => 'Robert',
1419 surname => '[% USE Categories %][% Categories.all().search_related("borrowers").count() %]',
1420 categorycode => $patron_category,
1421 branchcode => $library->{branchcode},
1422 dateofbirth => $date,
1423 smsalertnumber => undef,
1425 )->store->borrowernumber;
1427 my $title = q|<<branches.branchname>> - <<status>>|;
1428 my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>};
1431 q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES (?,'my module','tt test','my name',1,?,?,'email')|,
1432 undef, $library->{branchcode}, $title, $content
1436 borrowers => $borrowernumber,
1437 branches => $library->{branchcode},
1438 biblio => $biblionumber,
1441 status => 'overdue',
1443 my $prepared_letter = GetPreparedLetter(
1444 module => 'my module',
1445 branchcode => $library->{branchcode},
1446 letter_code => 'tt test',
1448 substitute => $substitute,
1453 $prepared_letter->{content},
1454 'Dear Robert [% USE Categories %][% Categories.all().search_related("borrowers").count() %]',
1455 'Template toolkit syntax in parameter was not evaluated.'