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'
908 # Note: What about includes, which language should be assumed 'default' here?
910 $letter = C4::Letters::GetPreparedLetter(
913 letter_code => 'code',
914 message_transport_type => 'email',
915 substitute => $substitute,
918 is( $letter->{title}, 'una prueba',
919 'GetPreparedLetter should return the required notice if it exists' );
921 $letter = C4::Letters::GetPreparedLetter(
924 letter_code => 'code',
925 message_transport_type => 'email',
926 substitute => $substitute,
932 'GetPreparedLetter should return the default notice if the one required does not exist'
935 t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
937 $letter = C4::Letters::GetPreparedLetter(
940 letter_code => 'code',
941 message_transport_type => 'email',
942 substitute => $substitute,
945 is( $letter->{title}, 'a test',
946 'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
948 t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
951 my $accountline = $builder->build(
953 source => 'Accountline',
955 borrowernumber => $borrowernumber,
957 amountoutstanding => $amount,
958 credit_type_code => 'PAYMENT',
964 INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
965 ('test', 'payment', '', 'Paiement du compte', 'Paiement du compte', "[% PROCESS 'accounts.inc' %][% PROCESS account_type_description account=credit %][% credit.description %]", 'print', 'fr-CA'),
966 ('test', 'payment', '', 'Default payment notice', 'Default payment notice', "[% PROCESS 'accounts.inc' %][% PROCESS account_type_description account=credit %][% credit.description %]", 'print', 'default');
970 t::lib::Mocks::mock_config( 'intrahtdocs', '/kohadevbox/koha/t/mock_templates/intranet-tmpl' );
973 borrowers => $borrowernumber,
974 credits => $accountline->{accountlines_id},
977 $letter = C4::Letters::GetPreparedLetter(
979 letter_code => 'payment',
980 message_transport_type => 'print',
984 is( $letter->{title}, 'Paiement du compte',
985 'GetPreparedLetter should return the notice in patron\'s preferred language' );
987 $letter->{content}, qr/Paiement/,
988 'Template includes should use the patron\'s preferred language too'
991 my $context = Test::MockModule->new('C4::Context');
992 $context->mock( 'interface', 'intranet' );
994 Koha::Cache::Memory::Lite->get_instance()->clear_from_cache('getlanguage');
995 t::lib::Mocks::mock_preference( 'language', 'fr-CA,en' );
997 $letter = C4::Letters::GetPreparedLetter(
999 letter_code => 'payment',
1000 message_transport_type => 'print',
1004 is( $letter->{title}, 'Default payment notice',
1005 'GetPreparedLetter should return the notice in default language' );
1006 like( $letter->{content}, qr/Paiement/, 'GetPreparedLetter should return the notice in the interface language' );
1008 $context->mock( 'interface', 'cron' );
1010 $letter = C4::Letters::GetPreparedLetter(
1012 letter_code => 'payment',
1013 message_transport_type => 'print',
1017 is( $letter->{title}, 'Default payment notice',
1018 'GetPreparedLetter should return the notice in default language' );
1020 $letter->{content}, qr/Paiement/,
1021 'GetPreparedLetter should return the notice in the first language in language system preference'
1025 subtest 'Test SMS handling in SendQueuedMessages' => sub {
1029 t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
1030 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
1032 my $patron = Koha::Patrons->find($borrowernumber);
1034 INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
1035 VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
1036 |, undef, $borrowernumber
1038 eval { C4::Letters::SendQueuedMessages(); };
1039 is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
1041 my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
1042 $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
1043 $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
1045 warning_like { C4::Letters::SendQueuedMessages(); }
1046 qr|Fake send_or_die|,
1047 "SendAlerts is using the mocked send_or_die routine (claimissues)";
1049 my $message = $schema->resultset('MessageQueue')->search({
1050 borrowernumber => $borrowernumber,
1054 is( $message->letter_id, $messages->[0]->{id}, "Message letter_id is set correctly" );
1055 is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
1057 $message->from_address(),
1059 'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
1062 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1064 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
1066 $message_id = C4::Letters::EnqueueLetter($my_message);
1067 warning_like { C4::Letters::SendQueuedMessages(); }
1068 qr|Fake send_or_die|,
1069 "SendAlerts is using the mocked send_or_die routine (claimissues)";
1071 $message = $schema->resultset('MessageQueue')->search({
1072 borrowernumber => $borrowernumber,
1077 $message->from_address(),
1078 'override@example.com',
1079 'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
1082 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
1083 $my_message->{to_address} = 'fixme@kidclamp.iswrong';
1084 $message_id = C4::Letters::EnqueueLetter($my_message);
1086 my $number_attempted = C4::Letters::SendQueuedMessages({
1087 borrowernumber => -1, # -1 still triggers the borrowernumber condition
1088 letter_code => 'PASSWORD_RESET',
1090 is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
1092 warning_like { C4::Letters::SendQueuedMessages(); }
1093 qr|Fake send_or_die|,
1094 "SendAlerts is using the mocked send_or_die routine (claimissues)";
1096 my $sms_message_address = $schema->resultset('MessageQueue')->search({
1097 borrowernumber => $borrowernumber,
1099 })->next()->to_address();
1100 is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
1102 # Test using SMS::Send::Test driver that's bundled with SMS::Send
1103 t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
1105 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1106 C4::Letters::EnqueueLetter($my_message);
1107 C4::Letters::SendQueuedMessages();
1109 $sms_message_address = $schema->resultset('MessageQueue')->search({
1110 borrowernumber => $borrowernumber,
1112 })->next()->to_address();
1113 is( $sms_message_address, '5555555555', 'SendQueuedMessages populates the to address correctly for SMS by SMS::Send driver to smsalertnumber when to_address is set incorrectly' );
1115 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
1118 subtest 'Test guarantor handling in SendQueuedMessages' => sub {
1122 t::lib::Mocks::mock_preference( 'borrowerRelationship', 'test' );
1123 t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 1 );
1125 my $patron = Koha::Patrons->find($borrowernumber);
1127 my $patron_category =
1128 $builder->build( { source => 'Category', value => { category_type => 'A', can_be_guarantee => 0 } } )
1130 my $guarantor1 = $builder->build_object(
1131 { class => 'Koha::Patrons', value => { email => 'g1@email.com', categorycode => $patron_category } } );
1132 my $guarantor2 = $builder->build_object(
1133 { class => 'Koha::Patrons', value => { email => 'g2@email.com', categorycode => $patron_category } } );
1135 $patron->add_guarantor( { guarantor_id => $guarantor1->borrowernumber, relationship => 'test' } );
1136 $patron->add_guarantor( { guarantor_id => $guarantor2->borrowernumber, relationship => 'test' } );
1140 'content' => 'a message',
1141 'metadata' => 'metadata',
1142 'code' => 'TEST_MESSAGE',
1143 'content_type' => 'text/plain',
1144 'title' => 'message title'
1146 'borrowernumber' => $borrowernumber,
1147 'to_address' => undef,
1148 'message_transport_type' => 'email',
1149 'from_address' => 'from@example.com'
1151 $message_id = C4::Letters::EnqueueLetter($my_message);
1154 t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '0' );
1156 warning_like { C4::Letters::SendQueuedMessages(); }
1157 qr|No 'to_address', email address or guarantors email address for borrowernumber|,
1158 "SendQueuedMessages fails when no to_address, patron notice email and RedirectGuaranteeEmail is not set";
1161 t::lib::Mocks::mock_preference( 'RedirectGuaranteeEmail', '1' );
1163 # reset message - testing without to or borrower valid email
1164 Koha::Notice::Messages->find($message_id)->delete;
1165 $message_id = C4::Letters::EnqueueLetter($my_message);
1167 warning_like { C4::Letters::SendQueuedMessages(); }
1168 qr|Fake send_or_die|,
1169 "SendQueuedMessages is using the mocked send_or_die routine";
1171 $message = $schema->resultset('MessageQueue')->search(
1173 borrowernumber => $borrowernumber,
1179 $message->to_address(),
1181 'SendQueuedMessages uses first guarantor email for "to" when patron has no email'
1185 $message->cc_address(),
1187 'SendQueuedMessages sets cc address to second guarantor email when "to" takes first guarantor email'
1190 is( $email_object->email->header('To'), $guarantor1->email, "mailto correctly uses first guarantor" );
1191 is( $email_object->email->header('Cc'), $guarantor2->email, "cc correctly uses second guarantor" );
1193 # reset message - testing borrower with valid email
1194 Koha::Notice::Messages->find($message_id)->delete;
1195 $message_id = C4::Letters::EnqueueLetter($my_message);
1197 $patron->email('patron@example.com')->store();
1199 warning_like { C4::Letters::SendQueuedMessages(); }
1200 qr|Fake send_or_die|,
1201 "SendQueuedMessages is using the mocked send_or_die routine";
1203 $message = $schema->resultset('MessageQueue')->search(
1205 borrowernumber => $borrowernumber,
1211 $message->to_address(),
1213 'SendQueuedMessages uses patron email when defined'
1217 $message->cc_address(),
1218 $guarantor1->email.",".$guarantor2->email,
1219 'SendQueuedMessages sets cc address to both guarantor emails when patron has email defined'
1222 is( $email_object->email->header('To'), $patron->email, "mailto correctly uses patrons email address" );
1223 is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1226 # reset message - testing explicit to passed to enqueue
1227 Koha::Notice::Messages->find($message_id)->delete;
1228 $my_message->{'to_address'} = 'to@example.com';
1229 $message_id = C4::Letters::EnqueueLetter($my_message);
1231 warning_like { C4::Letters::SendQueuedMessages(); }
1232 qr|Fake send_or_die|,
1233 "SendQueuedMessages is using the mocked send_or_die routine";
1235 $message = $schema->resultset('MessageQueue')->search(
1237 borrowernumber => $borrowernumber,
1243 $message->to_address(),
1245 'SendQueuedMessages uses to_address if it was specified at enqueue time'
1249 $message->cc_address(),
1250 $guarantor1->email.",".$guarantor2->email,
1251 'SendQueuedMessages sets cc address to both guarantor emails when "to" is already specified'
1254 is( $email_object->email->header('To'), 'to@example.com', "mailto correctly uses passed email" );
1255 is( $email_object->email->header('Cc'), $guarantor1->email.", ".$guarantor2->email, "cc correctly uses both guarantors" );
1257 # clear borrower queue
1258 Koha::Notice::Messages->find($message_id)->delete;
1260 t::lib::Mocks::mock_preference( 'ChildNeedsGuarantor', 0 );
1263 subtest 'get_item_content' => sub {
1266 t::lib::Mocks::mock_preference('dateformat', 'metric');
1267 t::lib::Mocks::mock_preference('timeformat', '24hr');
1269 {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
1270 {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
1272 my @item_content_fields = qw( date_due title barcode author itemnumber );
1275 for my $item ( @items ) {
1276 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
1279 my $expected_items_content = <<EOF;
1280 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
1281 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
1283 is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
1286 $items_content = q||;
1287 for my $item ( @items ) {
1288 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
1291 $expected_items_content = <<EOF;
1292 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
1293 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
1295 is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
1298 subtest 'Test where parameter for SendQueuedMessages' => sub {
1301 my $dbh = C4::Context->dbh;
1303 my $borrowernumber = Koha::Patron->new({
1304 firstname => 'Jane',
1306 categorycode => $patron_category,
1307 branchcode => $library->{branchcode},
1308 dateofbirth => $date,
1309 })->store->borrowernumber;
1311 $dbh->do(q|DELETE FROM message_queue|);
1314 'content' => 'a message',
1315 'metadata' => 'metadata',
1316 'code' => 'TEST_MESSAGE',
1317 'content_type' => 'text/plain',
1318 'title' => 'message title'
1320 'borrowernumber' => $borrowernumber,
1321 'to_address' => undef,
1322 'message_transport_type' => 'sms',
1323 'from_address' => 'from@example.com'
1327 'content' => 'another message',
1328 'metadata' => 'metadata',
1329 'code' => 'TEST_MESSAGE',
1330 'content_type' => 'text/plain',
1331 'title' => 'message title'
1333 'borrowernumber' => $borrowernumber,
1334 'to_address' => undef,
1335 'message_transport_type' => 'sms',
1336 'from_address' => 'from@example.com'
1340 'content' => 'a skipped message',
1341 'metadata' => 'metadata',
1342 'code' => 'TEST_MESSAGE',
1343 'content_type' => 'text/plain',
1344 'title' => 'message title'
1346 'borrowernumber' => $borrowernumber,
1347 'to_address' => undef,
1348 'message_transport_type' => 'sms',
1349 'from_address' => 'from@example.com'
1351 my @id = ( C4::Letters::EnqueueLetter($my_message),
1352 C4::Letters::EnqueueLetter($my_message2),
1353 C4::Letters::EnqueueLetter($my_message3),
1355 C4::Letters::SendQueuedMessages({
1356 # Test scalar/arrayref in parameter too
1357 letter_code => [ 'TEST_MESSAGE', 'SOMETHING_NOT_THERE' ],
1359 where => q{content NOT LIKE '%skipped%'},
1361 is( Koha::Notice::Messages->find( $id[0] )->status, 'failed', 'Processed but failed' );
1362 is( Koha::Notice::Messages->find( $id[1] )->status, 'failed', 'Processed but failed' );
1363 is( Koha::Notice::Messages->find( $id[2] )->status, 'pending', 'Skipped, still pending' );
1366 subtest 'Test limit 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 email => 'shouldnotwork@wrong.net',
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' => undef,
1391 'message_transport_type' => 'email',
1392 'from_address' => 'from@example.com'
1394 C4::Letters::EnqueueLetter($my_message) for 1..5;
1396 $send_or_die_count = 0; # reset
1398 my $regex = qr|Fake send_or_die|;
1400 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 1 } ) }
1402 "SendQueuedMessages with limit 1";
1403 is( $messages_sent, 1,
1404 'Sent 1 message with limit of 1 and 5 unprocessed messages' );
1407 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 2 } ) }
1408 [ map { $regex } 1..2 ],
1409 "SendQueuedMessages with limit 2";
1410 is( $messages_sent, 2,
1411 'Sent 2 messages with limit of 2 and 4 unprocessed messages' );
1414 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 3 } ) }
1415 [ map { $regex } 1..2 ],
1416 "SendQueuedMessages with limit 3";
1417 is( $messages_sent, 2,
1418 'Sent 2 messages with limit of 3 and 2 unprocessed messages' );
1420 is( $send_or_die_count, 5, '5 messages sent' );
1421 # Mimic correct status in queue for next tests
1422 Koha::Notice::Messages->search({ to_address => { 'LIKE', '%wrong.net' }})->update({ status => 'sent' });
1424 # Now add a few domain limits too, sending 2 more mails to wrongnet, 2 to fake1, 2 to fake2
1425 # Since we already sent 5 to wrong.net, we expect one deferral when limiting to 6
1426 # Similarly we arrange for 1 deferral for fake1, and 2 for fake2
1427 # We set therefore limit to 3 sent messages: we expect 2 sent, 4 deferred (so 6 processed)
1428 t::lib::Mocks::mock_config( 'message_domain_limits', { domain => [
1429 { name => 'wrong.net', limit => 6, unit => '1m' },
1430 { name => 'fake1.domain', limit => 1, unit => '1m' },
1431 { name => 'fake2.domain', limit => 0, unit => '1m' },
1433 C4::Letters::EnqueueLetter($my_message) for 1..2;
1434 $my_message->{to_address} = 'someone@fake1.domain';
1435 C4::Letters::EnqueueLetter($my_message) for 1..2;
1436 $my_message->{to_address} = 'another@fake2.domain';
1437 C4::Letters::EnqueueLetter($my_message) for 1..2;
1438 my $mocked_util = Test::MockModule->new('Koha::Notice::Util');
1439 my $count_exceeds_calls = 0;
1440 $mocked_util->mock( 'exceeds_limit', sub {
1441 $count_exceeds_calls++;
1442 $mocked_util->original('exceeds_limit')->(@_);
1445 $messages_sent = C4::Letters::SendQueuedMessages({ limit => 3 }) }
1446 [ qr/wrong.net reached limit/, $regex, qr/fake1.domain reached limit/, $regex ],
1447 "SendQueuedMessages with limit 2 and domain limits";
1448 is( $messages_sent, 2, 'Only expecting 2 sent messages' );
1449 is( Koha::Notice::Messages->search({ status => 'pending' })->count, 4, 'Still 4 pending' );
1450 is( $count_exceeds_calls, 6, 'We saw 6 messages while checking domain limits: so we deferred 4' );
1453 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1457 my $dbh = C4::Context->dbh;
1459 my $borrowernumber = Koha::Patron->new({
1460 firstname => 'Jane',
1462 categorycode => $patron_category,
1463 branchcode => $library->{branchcode},
1464 dateofbirth => $date,
1465 smsalertnumber => undef,
1466 })->store->borrowernumber;
1468 $dbh->do(q|DELETE FROM message_queue|);
1471 'content' => 'a message',
1472 'metadata' => 'metadata',
1473 'code' => 'TEST_MESSAGE',
1474 'content_type' => 'text/plain',
1475 'title' => 'message title'
1477 'borrowernumber' => $borrowernumber,
1478 'to_address' => 'to@example.org',
1479 'message_transport_type' => 'email',
1480 'from_address' => '@example.com' # invalid from_address
1482 my $message_id = C4::Letters::EnqueueLetter($my_message);
1483 $send_or_die_count = 0; # reset
1484 my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1485 is( $send_or_die_count, 0, 'Nothing sent when one message_id passed' );
1486 my $message_1 = C4::Letters::GetMessage($message_id);
1487 is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1488 is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1490 $my_message->{from_address} = 'root@example.org'; # valid from_address
1491 $message_id = C4::Letters::EnqueueLetter($my_message);
1492 warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1493 qr|Fake send_or_die|,
1494 "SendQueuedMessages is using the mocked send_or_die routine";
1495 is( $send_or_die_count, 1, 'One message passed through' );
1496 $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1497 my $message_2 = C4::Letters::GetMessage($message_id);
1498 is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1499 is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
1502 subtest 'Template toolkit syntax in parameters' => sub {
1504 my $borrowernumber = Koha::Patron->new(
1506 firstname => 'Robert',
1507 surname => '[% USE Categories %][% Categories.all().search_related("borrowers").count() %]',
1508 categorycode => $patron_category,
1509 branchcode => $library->{branchcode},
1510 dateofbirth => $date,
1511 smsalertnumber => undef,
1513 )->store->borrowernumber;
1515 my $title = q|<<branches.branchname>> - <<status>>|;
1516 my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>};
1519 q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES (?,'my module','tt test','my name',1,?,?,'email')|,
1520 undef, $library->{branchcode}, $title, $content
1524 borrowers => $borrowernumber,
1525 branches => $library->{branchcode},
1526 biblio => $biblionumber,
1529 status => 'overdue',
1531 my $prepared_letter = GetPreparedLetter(
1532 module => 'my module',
1533 branchcode => $library->{branchcode},
1534 letter_code => 'tt test',
1536 substitute => $substitute,
1541 $prepared_letter->{content},
1542 'Dear Robert [% USE Categories %][% Categories.all().search_related("borrowers").count() %]',
1543 'Template toolkit syntax in parameter was not evaluated.'