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 => 87;
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;
161 my $messages_processed = C4::Letters::SendQueuedMessages( { type => 'email' });
162 is($messages_processed, 0, 'No queued messages processed if type limit passed with unused type');
163 $messages_processed = C4::Letters::SendQueuedMessages( { type => 'sms' });
164 is($messages_processed, 0, 'All queued messages processed, nothing sent');
165 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
167 $messages->[0]->{status},
169 'message marked failed if tried to send SMS message for borrower with no smsalertnumber set (bug 11208)'
172 $messages->[0]->{failure_code},
174 'Correct failure code set for borrower with no smsalertnumber set'
176 isnt($messages->[0]->{updated_on}, $messages->[0]->{time_queued}, 'Time status changed differs from time queued when status changes' );
177 is(dt_from_string($messages->[0]->{time_queued}), $yesterday, 'Time queued remaines inmutable' );
180 my $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
181 my $message = C4::Letters::GetMessage( $messages->[0]->{message_id});
182 is( $resent, 1, 'The message should have been resent' );
183 is($message->{status},'pending', 'ResendMessage sets status to pending correctly (bug 12426)');
184 $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
185 is( $resent, 0, 'The message should not have been resent again' );
186 $resent = C4::Letters::ResendMessage();
187 is( $resent, undef, 'ResendMessage should return undef if not message_id given' );
190 is( $messages->[0]->{failure_code}, 'MISSING_SMS', 'Failure code set correctly for no smsalertnumber correctly set' );
193 my $letters = C4::Letters::GetLetters();
194 is( @$letters, 0, 'GetLetters returns the correct number of letters' );
196 my $title = q|<<branches.branchname>> - <<status>>|;
197 my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>,
198 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.
200 <<branches.branchname>>
201 <<branches.branchaddress1>>
204 The following item(s) is/are currently <<status>>:
206 <item> <<count>>. <<items.itemcallnumber>>, Barcode: <<items.barcode>> </item>
208 Thank-you for your prompt attention to this matter.
209 Don't forget your date of birth: <<borrowers.dateofbirth>>.
210 Look at this wonderful biblio timestamp: <<biblio.timestamp>>.
213 $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 );
214 $letters = C4::Letters::GetLetters();
215 is( @$letters, 1, 'GetLetters returns the correct number of letters' );
216 is( $letters->[0]->{module}, 'my module', 'GetLetters gets the module correctly' );
217 is( $letters->[0]->{code}, 'my code', 'GetLetters gets the code correctly' );
218 is( $letters->[0]->{name}, 'my name', 'GetLetters gets the name correctly' );
221 t::lib::Mocks::mock_preference('OPACBaseURL', 'http://thisisatest.com');
222 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
224 my $sms_content = 'This is a SMS for an <<status>>';
225 $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 );
228 borrowers => $borrowernumber,
229 branches => $library->{branchcode},
230 biblio => $biblionumber,
237 itemcallnumber => 'my callnumber1',
241 itemcallnumber => 'my callnumber2',
245 my $prepared_letter = GetPreparedLetter((
246 module => 'my module',
247 branchcode => $library->{branchcode},
248 letter_code => 'my code',
250 substitute => $substitute,
253 my $retrieved_library = Koha::Libraries->find($library->{branchcode});
254 my $my_title_letter = $retrieved_library->branchname . qq| - $substitute->{status}|;
255 my $biblio_timestamp = dt_from_string( GetBiblioData($biblionumber)->{timestamp} );
256 my $my_content_letter = qq|Dear Jane Smith,
257 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.
259 |.$retrieved_library->branchname.qq|
260 |.$retrieved_library->branchaddress1.qq|
261 URL: http://thisisatest.com
263 The following item(s) is/are currently $substitute->{status}:
265 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
266 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
268 Thank-you for your prompt attention to this matter.
269 Don't forget your date of birth: | . output_pref({ dt => $date, dateonly => 1 }) . q|.
270 Look at this wonderful biblio timestamp: | . output_pref({ dt => $biblio_timestamp }) . ".\n";
272 is( $prepared_letter->{title}, $my_title_letter, 'GetPreparedLetter returns the title correctly' );
273 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
275 $prepared_letter = GetPreparedLetter((
276 module => 'my module',
277 branchcode => $library->{branchcode},
278 letter_code => 'my code',
280 substitute => $substitute,
282 message_transport_type => 'sms',
284 $my_content_letter = qq|This is a SMS for an $substitute->{status}|;
285 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
288 $prepared_letter = GetPreparedLetter((
289 module => 'my module',
290 branchcode => $library->{branchcode},
291 letter_code => 'my code',
293 substitute => { status => undef },
295 message_transport_type => 'sms',
298 undef, "No warning if GetPreparedLetter called with substitute containing undefined value";
299 is( $prepared_letter->{content}, q|This is a SMS for an |, 'GetPreparedLetter returns the content correctly when substitute contains undefined value' );
301 $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>>.');});
302 $prepared_letter = GetPreparedLetter((
303 module => 'test_date',
305 letter_code => 'test_date',
307 substitute => $substitute,
310 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
312 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp | dateonly>>.' WHERE code = 'test_date';});
313 $prepared_letter = GetPreparedLetter((
314 module => 'test_date',
316 letter_code => 'test_date',
318 substitute => $substitute,
321 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
323 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp|dateonly >>.' WHERE code = 'test_date';});
324 $prepared_letter = GetPreparedLetter((
325 module => 'test_date',
327 letter_code => 'test_date',
329 substitute => $substitute,
332 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
334 t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
335 my $yesterday_night = $date->clone->add( days => -1 )->set_hour(22);
336 $dbh->do(q|UPDATE biblio SET timestamp = ? WHERE biblionumber = ?|, undef, $yesterday_night, $biblionumber );
337 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp>>.' WHERE code = 'test_date';});
338 $prepared_letter = GetPreparedLetter((
339 module => 'test_date',
341 letter_code => 'test_date',
343 substitute => $substitute,
346 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
348 $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>');});
349 $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 %]');});
351 my $testacqorder2_content = <<EOF;
353 [% bookseller.name %]
354 [% FOREACH order IN orders %]
355 Ordernumber [% order.ordernumber %] [% order.quantity %] [% order.listprice | \$Price %]
359 $dbh->do("INSERT INTO letter (module, code, name, title, content) VALUES ('orderacquisition','TESTACQORDER2','Acquisition Order','Order','$testacqorder2_content');");
361 my $popito = $builder->build({
362 source => 'Aqbookseller',
363 value => { name => 'Popito' }
366 my $order_1 = $builder->build({
374 my $order_2 = $builder->build({
382 $prepared_letter = GetPreparedLetter((
383 module => 'orderacquisition',
385 letter_code => 'TESTACQORDER2',
386 tables => { 'aqbooksellers' => $popito->{id} },
388 aqorders => [ $order_1->{ordernumber}, $order_2->{ordernumber} ]
392 my $testacqorder2_expected = qq|Popito
394 Ordernumber | . $order_1->{ordernumber} . qq| 2 12.00
396 Ordernumber | . $order_2->{ordernumber} . qq| 1 23.50
400 is($prepared_letter->{content}, $testacqorder2_expected);
402 # Test that _parseletter doesn't modify its parameters bug 15429
404 my $values = { dateexpiry => '2015-12-13', };
405 C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
406 is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
409 # Correctly format dateexpiry
411 my $values = { dateexpiry => '2015-12-13', };
413 t::lib::Mocks::mock_preference('dateformat', 'metric');
414 t::lib::Mocks::mock_preference('timeformat', '24hr');
415 my $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
416 is( $letter->{content}, 'expiry on 13/12/2015' );
418 t::lib::Mocks::mock_preference('dateformat', 'metric');
419 t::lib::Mocks::mock_preference('timeformat', '12hr');
420 $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
421 is( $letter->{content}, 'expiry on 13/12/2015' );
424 my $bookseller = Koha::Acquisition::Bookseller->new(
427 address1 => "bookseller's address",
433 my $booksellerid = $bookseller->id;
435 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith', phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
436 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
437 my $basketno = NewBasket($booksellerid, 1, 'The basket name');
439 my $budgetid = C4::Budgets::AddBudget({
440 budget_code => "budget_code_test_letters",
441 budget_name => "budget_name_test_letters",
444 my $bib = MARC::Record->new();
445 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
447 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
451 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
455 my $logged_in_user = $builder->build_object(
457 class => 'Koha::Patrons',
459 branchcode => $library->{branchcode},
460 email => 'some@email.com'
465 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
467 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
468 my $order = Koha::Acquisition::Order->new(
470 basketno => $basketno,
472 biblionumber => $biblionumber,
473 budget_id => $budgetid,
476 my $ordernumber = $order->ordernumber;
478 Koha::Acquisition::Baskets->find( $basketno )->close;
481 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
482 qr/^Bookseller .* without emails at/,
483 "SendAlerts prints a warning";
484 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
486 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
487 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
489 # Ensure that the preference 'ClaimsLog' is set to logging
490 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
492 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
493 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
497 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
498 qr|Fake send_or_die|,
499 "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
500 is($err, 1, "Successfully sent order.");
501 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent order");
502 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered) Basket name: The basket name', 'Order notice text constructed successfully');
504 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
505 $mocked_koha_email->mock( 'send_or_die', sub {
506 Email::Sender::Failure->throw('something went wrong');
510 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ); }
511 qr{something went wrong},
512 'Warning is printed';
514 is($err->{error}, 'something went wrong', "Send exception, error message returned");
516 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
518 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
519 qr/No orderacquisition TESTACQORDER letter transported by email/,
520 "GetPreparedLetter warns about missing notice template";
521 is($err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined.");
526 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
527 qr|Fake send_or_die|,
528 "SendAlerts is using the mocked send_or_die routine";
530 is($err, 1, "Successfully sent claim");
531 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
532 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
534 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
535 $mocked_koha_email->mock( 'send_or_die', sub {
536 Email::Sender::Failure->throw('something went wrong');
540 $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
541 qr{something went wrong},
542 'Warning is printed';
544 is($err->{error}, 'something went wrong', "Send exception, error message returned");
548 use C4::Serials qw( NewSubscription GetSerials findSerialsByStatus ModSerialStatus );
551 my $internalnotes = 'intnotes';
552 my $number_pattern = $builder->build_object(
554 class => 'Koha::Subscription::Numberpatterns',
555 value => { numberingmethod => 'No. {X}' }
558 my $subscriptionid = NewSubscription(
559 undef, "", undef, undef, undef, $biblionumber,
560 '2013-01-01', 1, undef, undef, undef,
561 undef, undef, undef, undef, undef, undef,
562 1, $notes,undef, '2013-01-01', undef, $number_pattern->id,
563 undef, undef, 0, $internalnotes, 0,
564 undef, undef, 0, undef, '2013-12-31', 0
566 $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>>');});
567 my ($serials_count, @serials) = GetSerials($subscriptionid);
568 my $serial = $serials[0];
570 my $patron = Koha::Patron->new({
573 categorycode => $patron_category,
574 branchcode => $library->{branchcode},
575 dateofbirth => $date,
576 email => 'john.smith@test.de',
578 my $borrowernumber = $patron->borrowernumber;
579 my $subscription = Koha::Subscriptions->find( $subscriptionid );
580 $subscription->add_subscriber( $patron );
582 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
585 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
586 qr|Fake send_or_die|,
587 "SendAlerts is using the mocked send_or_die routine";
589 is($err2, 1, "Successfully sent serial notification");
590 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
591 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
593 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
597 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
598 qr|Fake send_or_die|,
599 "SendAlerts is using the mocked send_or_die routine";
600 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
602 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
603 $mocked_koha_email->mock( 'send_or_die', sub {
604 Email::Sender::Failure->throw('something went wrong');
608 $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
609 qr{something went wrong},
610 'Warning is printed';
612 is($err->{error}, 'something went wrong', "Send exception, error message returned");
615 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
617 subtest 'SendAlerts - claimissue' => sub {
622 $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>>');});
624 my $bookseller = Koha::Acquisition::Bookseller->new(
627 address1 => "bookseller's address",
633 my $booksellerid = $bookseller->id;
635 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
637 my $bib = MARC::Record->new();
638 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
640 MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
641 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
645 MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
646 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
649 my ($biblionumber) = AddBiblio($bib, '');
651 my $number_pattern = $builder->build_object(
653 class => 'Koha::Subscription::Numberpatterns',
654 value => { numberingmethod => 'No. {X}' }
658 my $subscriptionid = NewSubscription(
659 undef, "", $booksellerid, undef, undef, $biblionumber,
660 '2013-01-01', 1, undef, undef, undef,
661 undef, undef, undef, undef, undef, undef,
662 1, 'public',undef, '2013-01-01', undef, $number_pattern->id,
663 undef, undef, 0, 'internal', 0,
664 undef, undef, 0, undef, '2013-12-31', 0
667 my ($serials_count, @serials) = GetSerials($subscriptionid);
668 my @serialids = ($serials[0]->{serialid});
672 $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
673 qr/^Bookseller .* without emails at/,
674 "Warn on vendor without email address";
676 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
677 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
679 # Ensure that the preference 'ClaimsLog' is set to logging
680 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
682 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
683 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
685 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
689 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
690 qr|Fake send_or_die|,
691 "SendAlerts is using the mocked send_or_die routine (claimissues)";
692 is( $err, 1, "Successfully sent claim" );
693 is( $email_object->email->header('To'),
694 'testemail@mydomain.com', "mailto correct in sent claim" );
696 $email_object->email->body,
697 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
698 'Serial claim letter for 1 issue constructed successfully'
701 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
702 $mocked_koha_email->mock( 'send_or_die', sub {
703 Email::Sender::Failure->throw('something went wrong');
707 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
708 qr{something went wrong},
709 'Warning is printed';
711 is($err->{error}, 'something went wrong', "Send exception, error message returned");
715 my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
716 my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
717 ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
718 ($serials_count, @serials) = GetSerials($subscriptionid);
719 push @serialids, ($serials[1]->{serialid});
721 warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
722 qr|Fake send_or_die|,
723 "SendAlerts is using the mocked send_or_die routine (claimissues)";
726 $email_object->email->body,
727 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
728 . $email_object->email->crlf
729 . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
730 "Serial claim letter for 2 issues constructed successfully"
733 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
735 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
736 qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
737 "GetPreparedLetter warns about missing notice template";
738 is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
743 subtest 'GetPreparedLetter' => sub {
746 Koha::Notice::Template->new(
751 message_transport_type => 'email'
756 $letter = C4::Letters::GetPreparedLetter(
758 letter_code => 'test',
761 qr{^ERROR: nothing to substitute},
762 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
764 'No letter should be returned by GetPreparedLetter if something went wrong'
768 $letter = C4::Letters::GetPreparedLetter(
770 letter_code => 'test',
774 qr{^ERROR: nothing to substitute},
775 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
777 'No letter should be returned by GetPreparedLetter if something went wrong'
784 subtest 'TranslateNotices' => sub {
787 t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
791 INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
792 ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
793 ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
796 my $letter = C4::Letters::GetPreparedLetter(
799 letter_code => 'code',
800 message_transport_type => 'email',
801 substitute => $substitute,
806 'GetPreparedLetter should return the default one if the lang parameter is not provided'
809 $letter = C4::Letters::GetPreparedLetter(
812 letter_code => 'code',
813 message_transport_type => 'email',
814 substitute => $substitute,
817 is( $letter->{title}, 'una prueba',
818 'GetPreparedLetter should return the required notice if it exists' );
820 $letter = C4::Letters::GetPreparedLetter(
823 letter_code => 'code',
824 message_transport_type => 'email',
825 substitute => $substitute,
831 'GetPreparedLetter should return the default notice if the one required does not exist'
834 t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
836 $letter = C4::Letters::GetPreparedLetter(
839 letter_code => 'code',
840 message_transport_type => 'email',
841 substitute => $substitute,
844 is( $letter->{title}, 'a test',
845 'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
849 subtest 'Test SMS handling in SendQueuedMessages' => sub {
853 t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
854 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
856 my $patron = Koha::Patrons->find($borrowernumber);
858 INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
859 VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
860 |, undef, $borrowernumber
862 eval { C4::Letters::SendQueuedMessages(); };
863 is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
865 my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
866 $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
867 $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
869 warning_like { C4::Letters::SendQueuedMessages(); }
870 qr|Fake send_or_die|,
871 "SendAlerts is using the mocked send_or_die routine (claimissues)";
873 my $message = $schema->resultset('MessageQueue')->search({
874 borrowernumber => $borrowernumber,
878 is( $message->letter_id, $messages->[0]->{id}, "Message letter_id is set correctly" );
879 is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
881 $message->from_address(),
883 'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
886 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
888 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
890 $message_id = C4::Letters::EnqueueLetter($my_message);
891 warning_like { C4::Letters::SendQueuedMessages(); }
892 qr|Fake send_or_die|,
893 "SendAlerts is using the mocked send_or_die routine (claimissues)";
895 $message = $schema->resultset('MessageQueue')->search({
896 borrowernumber => $borrowernumber,
901 $message->from_address(),
902 'override@example.com',
903 'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
906 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
907 $my_message->{to_address} = 'fixme@kidclamp.iswrong';
908 $message_id = C4::Letters::EnqueueLetter($my_message);
910 my $number_attempted = C4::Letters::SendQueuedMessages({
911 borrowernumber => -1, # -1 still triggers the borrowernumber condition
912 letter_code => 'PASSWORD_RESET',
914 is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
916 warning_like { C4::Letters::SendQueuedMessages(); }
917 qr|Fake send_or_die|,
918 "SendAlerts is using the mocked send_or_die routine (claimissues)";
920 my $sms_message_address = $schema->resultset('MessageQueue')->search({
921 borrowernumber => $borrowernumber,
923 })->next()->to_address();
924 is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
926 # Test using SMS::Send::Test driver that's bundled with SMS::Send
927 t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
929 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
930 C4::Letters::EnqueueLetter($my_message);
931 C4::Letters::SendQueuedMessages();
933 $sms_message_address = $schema->resultset('MessageQueue')->search({
934 borrowernumber => $borrowernumber,
936 })->next()->to_address();
937 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' );
941 subtest 'get_item_content' => sub {
944 t::lib::Mocks::mock_preference('dateformat', 'metric');
945 t::lib::Mocks::mock_preference('timeformat', '24hr');
947 {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
948 {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
950 my @item_content_fields = qw( date_due title barcode author itemnumber );
953 for my $item ( @items ) {
954 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
957 my $expected_items_content = <<EOF;
958 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
959 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
961 is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
964 $items_content = q||;
965 for my $item ( @items ) {
966 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
969 $expected_items_content = <<EOF;
970 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
971 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
973 is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
976 subtest 'Test where parameter for SendQueuedMessages' => sub {
979 my $dbh = C4::Context->dbh;
981 my $borrowernumber = Koha::Patron->new({
984 categorycode => $patron_category,
985 branchcode => $library->{branchcode},
986 dateofbirth => $date,
987 })->store->borrowernumber;
989 $dbh->do(q|DELETE FROM message_queue|);
992 'content' => 'a message',
993 'metadata' => 'metadata',
994 'code' => 'TEST_MESSAGE',
995 'content_type' => 'text/plain',
996 'title' => 'message title'
998 'borrowernumber' => $borrowernumber,
999 'to_address' => undef,
1000 'message_transport_type' => 'sms',
1001 'from_address' => 'from@example.com'
1005 'content' => 'another message',
1006 'metadata' => 'metadata',
1007 'code' => 'TEST_MESSAGE',
1008 'content_type' => 'text/plain',
1009 'title' => 'message title'
1011 'borrowernumber' => $borrowernumber,
1012 'to_address' => undef,
1013 'message_transport_type' => 'sms',
1014 'from_address' => 'from@example.com'
1018 'content' => 'a skipped message',
1019 'metadata' => 'metadata',
1020 'code' => 'TEST_MESSAGE',
1021 'content_type' => 'text/plain',
1022 'title' => 'message title'
1024 'borrowernumber' => $borrowernumber,
1025 'to_address' => undef,
1026 'message_transport_type' => 'sms',
1027 'from_address' => 'from@example.com'
1029 my @id = ( C4::Letters::EnqueueLetter($my_message),
1030 C4::Letters::EnqueueLetter($my_message2),
1031 C4::Letters::EnqueueLetter($my_message3),
1033 C4::Letters::SendQueuedMessages({
1034 # Test scalar/arrayref in parameter too
1035 letter_code => [ 'TEST_MESSAGE', 'SOMETHING_NOT_THERE' ],
1037 where => q{content NOT LIKE '%skipped%'},
1039 is( Koha::Notice::Messages->find( $id[0] )->status, 'failed', 'Processed but failed' );
1040 is( Koha::Notice::Messages->find( $id[1] )->status, 'failed', 'Processed but failed' );
1041 is( Koha::Notice::Messages->find( $id[2] )->status, 'pending', 'Skipped, still pending' );
1044 subtest 'Test limit parameter for SendQueuedMessages' => sub {
1047 my $dbh = C4::Context->dbh;
1049 my $borrowernumber = Koha::Patron->new({
1050 firstname => 'Jane',
1052 categorycode => $patron_category,
1053 branchcode => $library->{branchcode},
1054 dateofbirth => $date,
1055 email => 'shouldnotwork@wrong.net',
1056 })->store->borrowernumber;
1058 $dbh->do(q|DELETE FROM message_queue|);
1061 'content' => 'a message',
1062 'metadata' => 'metadata',
1063 'code' => 'TEST_MESSAGE',
1064 'content_type' => 'text/plain',
1065 'title' => 'message title'
1067 'borrowernumber' => $borrowernumber,
1068 'to_address' => undef,
1069 'message_transport_type' => 'email',
1070 'from_address' => 'from@example.com'
1072 C4::Letters::EnqueueLetter($my_message) for 1..5;
1074 $send_or_die_count = 0; # reset
1076 my $regex = qr|Fake send_or_die|;
1078 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 1 } ) }
1080 "SendQueuedMessages with limit 1";
1081 is( $messages_sent, 1,
1082 'Sent 1 message with limit of 1 and 5 unprocessed messages' );
1085 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 2 } ) }
1086 [ map { $regex } 1..2 ],
1087 "SendQueuedMessages with limit 2";
1088 is( $messages_sent, 2,
1089 'Sent 2 messages with limit of 2 and 4 unprocessed messages' );
1092 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 3 } ) }
1093 [ map { $regex } 1..2 ],
1094 "SendQueuedMessages with limit 3";
1095 is( $messages_sent, 2,
1096 'Sent 2 messages with limit of 3 and 2 unprocessed messages' );
1098 is( $send_or_die_count, 5, '5 messages sent' );
1099 # Mimic correct status in queue for next tests
1100 Koha::Notice::Messages->search({ to_address => { 'LIKE', '%wrong.net' }})->update({ status => 'sent' });
1102 # Now add a few domain limits too, sending 2 more mails to wrongnet, 2 to fake1, 2 to fake2
1103 # Since we already sent 5 to wrong.net, we expect one deferral when limiting to 6
1104 # Similarly we arrange for 1 deferral for fake1, and 2 for fake2
1105 # We set therefore limit to 3 sent messages: we expect 2 sent, 4 deferred (so 6 processed)
1106 t::lib::Mocks::mock_config( 'message_domain_limits', { domain => [
1107 { name => 'wrong.net', limit => 6, unit => '1m' },
1108 { name => 'fake1.domain', limit => 1, unit => '1m' },
1109 { name => 'fake2.domain', limit => 0, unit => '1m' },
1111 C4::Letters::EnqueueLetter($my_message) for 1..2;
1112 $my_message->{to_address} = 'someone@fake1.domain';
1113 C4::Letters::EnqueueLetter($my_message) for 1..2;
1114 $my_message->{to_address} = 'another@fake2.domain';
1115 C4::Letters::EnqueueLetter($my_message) for 1..2;
1116 my $mocked_util = Test::MockModule->new('Koha::Notice::Util');
1117 my $count_exceeds_calls = 0;
1118 $mocked_util->mock( 'exceeds_limit', sub {
1119 $count_exceeds_calls++;
1120 $mocked_util->original('exceeds_limit')->(@_);
1123 $messages_sent = C4::Letters::SendQueuedMessages({ limit => 3 }) }
1124 [ qr/wrong.net reached limit/, $regex, qr/fake1.domain reached limit/, $regex ],
1125 "SendQueuedMessages with limit 2 and domain limits";
1126 is( $messages_sent, 2, 'Only expecting 2 sent messages' );
1127 is( Koha::Notice::Messages->search({ status => 'pending' })->count, 4, 'Still 4 pending' );
1128 is( $count_exceeds_calls, 6, 'We saw 6 messages while checking domain limits: so we deferred 4' );
1131 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1135 my $dbh = C4::Context->dbh;
1137 my $borrowernumber = Koha::Patron->new({
1138 firstname => 'Jane',
1140 categorycode => $patron_category,
1141 branchcode => $library->{branchcode},
1142 dateofbirth => $date,
1143 smsalertnumber => undef,
1144 })->store->borrowernumber;
1146 $dbh->do(q|DELETE FROM message_queue|);
1149 'content' => 'a message',
1150 'metadata' => 'metadata',
1151 'code' => 'TEST_MESSAGE',
1152 'content_type' => 'text/plain',
1153 'title' => 'message title'
1155 'borrowernumber' => $borrowernumber,
1156 'to_address' => 'to@example.org',
1157 'message_transport_type' => 'email',
1158 'from_address' => '@example.com' # invalid from_address
1160 my $message_id = C4::Letters::EnqueueLetter($my_message);
1161 $send_or_die_count = 0; # reset
1162 my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1163 is( $send_or_die_count, 0, 'Nothing sent when one message_id passed' );
1164 my $message_1 = C4::Letters::GetMessage($message_id);
1165 is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1166 is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1168 $my_message->{from_address} = 'root@example.org'; # valid from_address
1169 $message_id = C4::Letters::EnqueueLetter($my_message);
1170 warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1171 qr|Fake send_or_die|,
1172 "SendQueuedMessages is using the mocked send_or_die routine";
1173 is( $send_or_die_count, 1, 'One message passed through' );
1174 $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1175 my $message_2 = C4::Letters::GetMessage($message_id);
1176 is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1177 is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );