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 $budget_period_id = C4::Budgets::AddBudgetPeriod(
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"
476 my $budgetid = C4::Budgets::AddBudget(
478 budget_code => "budget_code_test_letters",
479 budget_name => "budget_name_test_letters",
480 budget_period_id => $budget_period_id,
484 my $bib = MARC::Record->new();
485 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
487 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
491 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
495 my $logged_in_user = $builder->build_object(
497 class => 'Koha::Patrons',
499 branchcode => $library->{branchcode},
500 email => 'some@email.com'
505 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
507 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
508 my $order = Koha::Acquisition::Order->new(
510 basketno => $basketno,
512 biblionumber => $biblionumber,
513 budget_id => $budgetid,
516 my $ordernumber = $order->ordernumber;
518 Koha::Acquisition::Baskets->find( $basketno )->close;
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");
526 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
527 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
529 # Ensure that the preference 'ClaimsLog' is set to logging
530 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
532 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
533 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
537 $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' )
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" );
545 $email_object->email->body,
546 'my vendor|John Smith|Ordernumber '
548 . ' (Silence in the library) (1 ordered) Basket name: The basket name',
549 'Order notice text constructed successfully'
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' );
557 $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' )
559 qr|Fake send_or_die|,
560 "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
562 $email_object->email->header('From'), 'acq-default@domain.com',
563 "AcquisitionsDefaultEmailAddress is used to sent acq notification"
566 $email_object->email->header('Reply-To'), 'acq-replyto@domain.com',
567 "AcquisitionsDefaultReplyTo is used to sent acq notification"
570 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
571 $mocked_koha_email->mock(
574 Email::Sender::Failure->throw('something went wrong');
579 $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' );
581 qr{something went wrong},
582 'Warning is printed';
584 is( $err->{error}, 'something went wrong', "Send exception, error message returned" );
586 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
588 $err = SendAlerts( 'orderacquisition', $basketno, 'TESTACQORDER' )
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." );
597 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
598 qr|Fake send_or_die|,
599 "SendAlerts is using the mocked send_or_die routine";
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');
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');
611 $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
612 qr{something went wrong},
613 'Warning is printed';
615 is($err->{error}, 'something went wrong', "Send exception, error message returned");
619 use C4::Serials qw( NewSubscription GetSerials findSerialsByStatus ModSerialStatus );
622 my $internalnotes = 'intnotes';
623 my $number_pattern = $builder->build_object(
625 class => 'Koha::Subscription::Numberpatterns',
626 value => { numberingmethod => 'No. {X}' }
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
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];
641 my $patron = Koha::Patron->new({
644 categorycode => $patron_category,
645 branchcode => $library->{branchcode},
646 dateofbirth => $date,
647 email => 'john.smith@test.de',
649 my $borrowernumber = $patron->borrowernumber;
650 my $subscription = Koha::Subscriptions->find( $subscriptionid );
651 $subscription->add_subscriber( $patron );
653 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
656 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
657 qr|Fake send_or_die|,
658 "SendAlerts is using the mocked send_or_die routine";
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');
664 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
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");
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');
679 $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
680 qr{something went wrong},
681 'Warning is printed';
683 is($err->{error}, 'something went wrong', "Send exception, error message returned");
686 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
688 subtest '_parseletter' => sub {
691 # Regression test for bug 10843
692 # $dt->add takes a scalar, not undef
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' );
702 subtest 'SendAlerts - claimissue' => sub {
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>>');});
709 my $bookseller = Koha::Acquisition::Bookseller->new(
712 address1 => "bookseller's address",
718 my $booksellerid = $bookseller->id;
720 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
722 my $bib = MARC::Record->new();
723 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
725 MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
726 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
730 MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
731 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
734 my ($biblionumber) = AddBiblio($bib, '');
736 my $number_pattern = $builder->build_object(
738 class => 'Koha::Subscription::Numberpatterns',
739 value => { numberingmethod => 'No. {X}' }
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
752 my ($serials_count, @serials) = GetSerials($subscriptionid);
753 my @serialids = ($serials[0]->{serialid});
757 $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
758 qr/^Bookseller .* without emails at/,
759 "Warn on vendor without email address";
761 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
762 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
764 # Ensure that the preference 'ClaimsLog' is set to logging
765 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
767 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
768 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
770 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
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" );
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'
788 t::lib::Mocks::mock_preference( 'SerialsDefaultEmailAddress', 'ser-default@domain.com' );
789 t::lib::Mocks::mock_preference( 'SerialsDefaultReplyTo', 'ser-replyto@domain.com' );
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" );
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');
807 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
808 qr{something went wrong},
809 'Warning is printed';
811 is($err->{error}, 'something went wrong', "Send exception, error message returned");
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});
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)";
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"
833 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
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");
843 subtest 'GetPreparedLetter' => sub {
846 Koha::Notice::Template->new(
851 message_transport_type => 'email'
856 $letter = C4::Letters::GetPreparedLetter(
858 letter_code => 'test',
861 qr{^ERROR: nothing to substitute},
862 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
864 'No letter should be returned by GetPreparedLetter if something went wrong'
868 $letter = C4::Letters::GetPreparedLetter(
870 letter_code => 'test',
874 qr{^ERROR: nothing to substitute},
875 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
877 'No letter should be returned by GetPreparedLetter if something went wrong'
884 subtest 'TranslateNotices' => sub {
887 t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
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');
896 my $letter = C4::Letters::GetPreparedLetter(
899 letter_code => 'code',
900 message_transport_type => 'email',
901 substitute => $substitute,
906 'GetPreparedLetter should return the default one if the lang parameter is not provided'
909 $letter = C4::Letters::GetPreparedLetter(
912 letter_code => 'code',
913 message_transport_type => 'email',
914 substitute => $substitute,
917 is( $letter->{title}, 'una prueba',
918 'GetPreparedLetter should return the required notice if it exists' );
920 $letter = C4::Letters::GetPreparedLetter(
923 letter_code => 'code',
924 message_transport_type => 'email',
925 substitute => $substitute,
931 'GetPreparedLetter should return the default notice if the one required does not exist'
934 t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
936 $letter = C4::Letters::GetPreparedLetter(
939 letter_code => 'code',
940 message_transport_type => 'email',
941 substitute => $substitute,
944 is( $letter->{title}, 'a test',
945 'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
949 subtest 'Test SMS handling in SendQueuedMessages' => sub {
953 t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
954 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
956 my $patron = Koha::Patrons->find($borrowernumber);
958 INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
959 VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
960 |, undef, $borrowernumber
962 eval { C4::Letters::SendQueuedMessages(); };
963 is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
965 my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
966 $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
967 $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
969 warning_like { C4::Letters::SendQueuedMessages(); }
970 qr|Fake send_or_die|,
971 "SendAlerts is using the mocked send_or_die routine (claimissues)";
973 my $message = $schema->resultset('MessageQueue')->search({
974 borrowernumber => $borrowernumber,
978 is( $message->letter_id, $messages->[0]->{id}, "Message letter_id is set correctly" );
979 is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
981 $message->from_address(),
983 'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
986 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
988 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
990 $message_id = C4::Letters::EnqueueLetter($my_message);
991 warning_like { C4::Letters::SendQueuedMessages(); }
992 qr|Fake send_or_die|,
993 "SendAlerts is using the mocked send_or_die routine (claimissues)";
995 $message = $schema->resultset('MessageQueue')->search({
996 borrowernumber => $borrowernumber,
1001 $message->from_address(),
1002 'override@example.com',
1003 'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
1006 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
1007 $my_message->{to_address} = 'fixme@kidclamp.iswrong';
1008 $message_id = C4::Letters::EnqueueLetter($my_message);
1010 my $number_attempted = C4::Letters::SendQueuedMessages({
1011 borrowernumber => -1, # -1 still triggers the borrowernumber condition
1012 letter_code => 'PASSWORD_RESET',
1014 is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
1016 warning_like { C4::Letters::SendQueuedMessages(); }
1017 qr|Fake send_or_die|,
1018 "SendAlerts is using the mocked send_or_die routine (claimissues)";
1020 my $sms_message_address = $schema->resultset('MessageQueue')->search({
1021 borrowernumber => $borrowernumber,
1023 })->next()->to_address();
1024 is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
1026 # Test using SMS::Send::Test driver that's bundled with SMS::Send
1027 t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
1029 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1030 C4::Letters::EnqueueLetter($my_message);
1031 C4::Letters::SendQueuedMessages();
1033 $sms_message_address = $schema->resultset('MessageQueue')->search({
1034 borrowernumber => $borrowernumber,
1036 })->next()->to_address();
1037 is( $sms_message_address, '5555555555', 'SendQueuedMessages populates the to address correctly for SMS by SMS::Send driver to smsalertnumber when to_address is set incorrectly' );
1039 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1042 subtest 'Test guarantor handling in SendQueuedMessages' => sub {
1046 t::lib::Mocks::mock_preference( 'borrowerRelationship', 'test' );
1047 t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 1 );
1049 my $patron = Koha::Patrons->find($borrowernumber);
1051 my $patron_category =
1052 $builder->build( { source => 'Category', value => { category_type => 'A', can_be_guarantee => 0 } } )
1054 my $guarantor1 = $builder->build_object(
1055 { class => 'Koha::Patrons', value => { email => 'g1@email.com', categorycode => $patron_category } } );
1056 my $guarantor2 = $builder->build_object(
1057 { class => 'Koha::Patrons', value => { email => 'g2@email.com', categorycode => $patron_category } } );
1059 $patron->add_guarantor( { guarantor_id => $guarantor1->borrowernumber, relationship => 'test' } );
1060 $patron->add_guarantor( { guarantor_id => $guarantor2->borrowernumber, relationship => 'test' } );
1064 'content' => 'a message',
1065 'metadata' => 'metadata',
1066 'code' => 'TEST_MESSAGE',
1067 'content_type' => 'text/plain',
1068 'title' => 'message title'
1070 'borrowernumber' => $borrowernumber,
1071 'to_address' => undef,
1072 'message_transport_type' => 'email',
1073 'from_address' => 'from@example.com'
1075 $message_id = C4::Letters::EnqueueLetter($my_message);
1078 t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '0' );
1080 warning_like { C4::Letters::SendQueuedMessages(); }
1081 qr|No 'to_address', email address or guarantors email address for borrowernumber|,
1082 "SendQueuedMessages fails when no to_address, patron notice email and RedirectGuaranteeEmail is not set";
1085 t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '1' );
1087 # reset message - testing without to or borrower valid email
1088 Koha::Notice::Messages->find($message_id)->delete;
1089 $message_id = C4::Letters::EnqueueLetter($my_message);
1091 warning_like { C4::Letters::SendQueuedMessages(); }
1092 qr|Fake send_or_die|,
1093 "SendQueuedMessages is using the mocked send_or_die routine";
1095 $message = $schema->resultset('MessageQueue')->search(
1097 borrowernumber => $borrowernumber,
1103 $message->to_address(),
1105 'SendQueuedMessages uses first guarantor email for "to" when patron has no email'
1109 $message->cc_address(),
1111 'SendQueuedMessages sets cc address to second guarantor email when "to" takes first guarantor email'
1114 is( $email_object->email->header('To'), $guarantor1->email, "mailto correctly uses first guarantor" );
1115 is( $email_object->email->header('Cc'), $guarantor2->email, "cc correctly uses second guarantor" );
1117 # reset message - testing borrower with valid email
1118 Koha::Notice::Messages->find($message_id)->delete;
1119 $message_id = C4::Letters::EnqueueLetter($my_message);
1121 $patron->email('patron@example.com')->store();
1123 warning_like { C4::Letters::SendQueuedMessages(); }
1124 qr|Fake send_or_die|,
1125 "SendQueuedMessages is using the mocked send_or_die routine";
1127 $message = $schema->resultset('MessageQueue')->search(
1129 borrowernumber => $borrowernumber,
1135 $message->to_address(),
1137 'SendQueuedMessages uses patron email when defined'
1141 $message->cc_address(),
1142 $guarantor1->email.",".$guarantor2->email,
1143 'SendQueuedMessages sets cc address to both guarantor emails when patron has email defined'
1146 is( $email_object->email->header('To'), $patron->email, "mailto correctly uses patrons email address" );
1147 is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1150 # reset message - testing explicit to passed to enqueue
1151 Koha::Notice::Messages->find($message_id)->delete;
1152 $my_message->{'to_address'} = 'to@example.com';
1153 $message_id = C4::Letters::EnqueueLetter($my_message);
1155 warning_like { C4::Letters::SendQueuedMessages(); }
1156 qr|Fake send_or_die|,
1157 "SendQueuedMessages is using the mocked send_or_die routine";
1159 $message = $schema->resultset('MessageQueue')->search(
1161 borrowernumber => $borrowernumber,
1167 $message->to_address(),
1169 'SendQueuedMessages uses to_address if it was specified at enqueue time'
1173 $message->cc_address(),
1174 $guarantor1->email.",".$guarantor2->email,
1175 'SendQueuedMessages sets cc address to both guarantor emails when "to" is already specified'
1178 is( $email_object->email->header('To'), 'to@example.com', "mailto correctly uses passed email" );
1179 is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1181 # clear borrower queue
1182 Koha::Notice::Messages->find($message_id)->delete;
1184 t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
1187 subtest 'get_item_content' => sub {
1190 t::lib::Mocks::mock_preference('dateformat', 'metric');
1191 t::lib::Mocks::mock_preference('timeformat', '24hr');
1193 {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
1194 {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
1196 my @item_content_fields = qw( date_due title barcode author itemnumber );
1199 for my $item ( @items ) {
1200 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
1203 my $expected_items_content = <<EOF;
1204 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
1205 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
1207 is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
1210 $items_content = q||;
1211 for my $item ( @items ) {
1212 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
1215 $expected_items_content = <<EOF;
1216 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
1217 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
1219 is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
1222 subtest 'Test where parameter for SendQueuedMessages' => sub {
1225 my $dbh = C4::Context->dbh;
1227 my $borrowernumber = Koha::Patron->new({
1228 firstname => 'Jane',
1230 categorycode => $patron_category,
1231 branchcode => $library->{branchcode},
1232 dateofbirth => $date,
1233 })->store->borrowernumber;
1235 $dbh->do(q|DELETE FROM message_queue|);
1238 'content' => 'a message',
1239 'metadata' => 'metadata',
1240 'code' => 'TEST_MESSAGE',
1241 'content_type' => 'text/plain',
1242 'title' => 'message title'
1244 'borrowernumber' => $borrowernumber,
1245 'to_address' => undef,
1246 'message_transport_type' => 'sms',
1247 'from_address' => 'from@example.com'
1251 'content' => 'another message',
1252 'metadata' => 'metadata',
1253 'code' => 'TEST_MESSAGE',
1254 'content_type' => 'text/plain',
1255 'title' => 'message title'
1257 'borrowernumber' => $borrowernumber,
1258 'to_address' => undef,
1259 'message_transport_type' => 'sms',
1260 'from_address' => 'from@example.com'
1264 'content' => 'a skipped message',
1265 'metadata' => 'metadata',
1266 'code' => 'TEST_MESSAGE',
1267 'content_type' => 'text/plain',
1268 'title' => 'message title'
1270 'borrowernumber' => $borrowernumber,
1271 'to_address' => undef,
1272 'message_transport_type' => 'sms',
1273 'from_address' => 'from@example.com'
1275 my @id = ( C4::Letters::EnqueueLetter($my_message),
1276 C4::Letters::EnqueueLetter($my_message2),
1277 C4::Letters::EnqueueLetter($my_message3),
1279 C4::Letters::SendQueuedMessages({
1280 # Test scalar/arrayref in parameter too
1281 letter_code => [ 'TEST_MESSAGE', 'SOMETHING_NOT_THERE' ],
1283 where => q{content NOT LIKE '%skipped%'},
1285 is( Koha::Notice::Messages->find( $id[0] )->status, 'failed', 'Processed but failed' );
1286 is( Koha::Notice::Messages->find( $id[1] )->status, 'failed', 'Processed but failed' );
1287 is( Koha::Notice::Messages->find( $id[2] )->status, 'pending', 'Skipped, still pending' );
1290 subtest 'Test limit parameter for SendQueuedMessages' => sub {
1293 my $dbh = C4::Context->dbh;
1295 my $borrowernumber = Koha::Patron->new({
1296 firstname => 'Jane',
1298 categorycode => $patron_category,
1299 branchcode => $library->{branchcode},
1300 dateofbirth => $date,
1301 email => 'shouldnotwork@wrong.net',
1302 })->store->borrowernumber;
1304 $dbh->do(q|DELETE FROM message_queue|);
1307 'content' => 'a message',
1308 'metadata' => 'metadata',
1309 'code' => 'TEST_MESSAGE',
1310 'content_type' => 'text/plain',
1311 'title' => 'message title'
1313 'borrowernumber' => $borrowernumber,
1314 'to_address' => undef,
1315 'message_transport_type' => 'email',
1316 'from_address' => 'from@example.com'
1318 C4::Letters::EnqueueLetter($my_message) for 1..5;
1320 $send_or_die_count = 0; # reset
1322 my $regex = qr|Fake send_or_die|;
1324 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 1 } ) }
1326 "SendQueuedMessages with limit 1";
1327 is( $messages_sent, 1,
1328 'Sent 1 message with limit of 1 and 5 unprocessed messages' );
1331 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 2 } ) }
1332 [ map { $regex } 1..2 ],
1333 "SendQueuedMessages with limit 2";
1334 is( $messages_sent, 2,
1335 'Sent 2 messages with limit of 2 and 4 unprocessed messages' );
1338 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 3 } ) }
1339 [ map { $regex } 1..2 ],
1340 "SendQueuedMessages with limit 3";
1341 is( $messages_sent, 2,
1342 'Sent 2 messages with limit of 3 and 2 unprocessed messages' );
1344 is( $send_or_die_count, 5, '5 messages sent' );
1345 # Mimic correct status in queue for next tests
1346 Koha::Notice::Messages->search({ to_address => { 'LIKE', '%wrong.net' }})->update({ status => 'sent' });
1348 # Now add a few domain limits too, sending 2 more mails to wrongnet, 2 to fake1, 2 to fake2
1349 # Since we already sent 5 to wrong.net, we expect one deferral when limiting to 6
1350 # Similarly we arrange for 1 deferral for fake1, and 2 for fake2
1351 # We set therefore limit to 3 sent messages: we expect 2 sent, 4 deferred (so 6 processed)
1352 t::lib::Mocks::mock_config( 'message_domain_limits', { domain => [
1353 { name => 'wrong.net', limit => 6, unit => '1m' },
1354 { name => 'fake1.domain', limit => 1, unit => '1m' },
1355 { name => 'fake2.domain', limit => 0, unit => '1m' },
1357 C4::Letters::EnqueueLetter($my_message) for 1..2;
1358 $my_message->{to_address} = 'someone@fake1.domain';
1359 C4::Letters::EnqueueLetter($my_message) for 1..2;
1360 $my_message->{to_address} = 'another@fake2.domain';
1361 C4::Letters::EnqueueLetter($my_message) for 1..2;
1362 my $mocked_util = Test::MockModule->new('Koha::Notice::Util');
1363 my $count_exceeds_calls = 0;
1364 $mocked_util->mock( 'exceeds_limit', sub {
1365 $count_exceeds_calls++;
1366 $mocked_util->original('exceeds_limit')->(@_);
1369 $messages_sent = C4::Letters::SendQueuedMessages({ limit => 3 }) }
1370 [ qr/wrong.net reached limit/, $regex, qr/fake1.domain reached limit/, $regex ],
1371 "SendQueuedMessages with limit 2 and domain limits";
1372 is( $messages_sent, 2, 'Only expecting 2 sent messages' );
1373 is( Koha::Notice::Messages->search({ status => 'pending' })->count, 4, 'Still 4 pending' );
1374 is( $count_exceeds_calls, 6, 'We saw 6 messages while checking domain limits: so we deferred 4' );
1377 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1381 my $dbh = C4::Context->dbh;
1383 my $borrowernumber = Koha::Patron->new({
1384 firstname => 'Jane',
1386 categorycode => $patron_category,
1387 branchcode => $library->{branchcode},
1388 dateofbirth => $date,
1389 smsalertnumber => undef,
1390 })->store->borrowernumber;
1392 $dbh->do(q|DELETE FROM message_queue|);
1395 'content' => 'a message',
1396 'metadata' => 'metadata',
1397 'code' => 'TEST_MESSAGE',
1398 'content_type' => 'text/plain',
1399 'title' => 'message title'
1401 'borrowernumber' => $borrowernumber,
1402 'to_address' => 'to@example.org',
1403 'message_transport_type' => 'email',
1404 'from_address' => '@example.com' # invalid from_address
1406 my $message_id = C4::Letters::EnqueueLetter($my_message);
1407 $send_or_die_count = 0; # reset
1408 my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1409 is( $send_or_die_count, 0, 'Nothing sent when one message_id passed' );
1410 my $message_1 = C4::Letters::GetMessage($message_id);
1411 is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1412 is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1414 $my_message->{from_address} = 'root@example.org'; # valid from_address
1415 $message_id = C4::Letters::EnqueueLetter($my_message);
1416 warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1417 qr|Fake send_or_die|,
1418 "SendQueuedMessages is using the mocked send_or_die routine";
1419 is( $send_or_die_count, 1, 'One message passed through' );
1420 $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1421 my $message_2 = C4::Letters::GetMessage($message_id);
1422 is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1423 is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
1426 subtest 'Template toolkit syntax in parameters' => sub {
1428 my $borrowernumber = Koha::Patron->new(
1430 firstname => 'Robert',
1431 surname => '[% USE Categories %][% Categories.all().search_related("borrowers").count() %]',
1432 categorycode => $patron_category,
1433 branchcode => $library->{branchcode},
1434 dateofbirth => $date,
1435 smsalertnumber => undef,
1437 )->store->borrowernumber;
1439 my $title = q|<<branches.branchname>> - <<status>>|;
1440 my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>};
1443 q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES (?,'my module','tt test','my name',1,?,?,'email')|,
1444 undef, $library->{branchcode}, $title, $content
1448 borrowers => $borrowernumber,
1449 branches => $library->{branchcode},
1450 biblio => $biblionumber,
1453 status => 'overdue',
1455 my $prepared_letter = GetPreparedLetter(
1456 module => 'my module',
1457 branchcode => $library->{branchcode},
1458 letter_code => 'tt test',
1460 substitute => $substitute,
1465 $prepared_letter->{content},
1466 'Dear Robert [% USE Categories %][% Categories.all().search_related("borrowers").count() %]',
1467 'Template toolkit syntax in parameter was not evaluated.'