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 $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
553 my $subscriptionid = NewSubscription(
554 undef, "", undef, undef, undef, $biblionumber,
555 '2013-01-01', 1, undef, undef, undef,
556 undef, undef, undef, undef, undef, undef,
557 1, $notes,undef, '2013-01-01', undef, 1,
558 undef, undef, 0, $internalnotes, 0,
559 undef, undef, 0, undef, '2013-12-31', 0
561 $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>>');});
562 my ($serials_count, @serials) = GetSerials($subscriptionid);
563 my $serial = $serials[0];
565 my $patron = Koha::Patron->new({
568 categorycode => $patron_category,
569 branchcode => $library->{branchcode},
570 dateofbirth => $date,
571 email => 'john.smith@test.de',
573 my $borrowernumber = $patron->borrowernumber;
574 my $subscription = Koha::Subscriptions->find( $subscriptionid );
575 $subscription->add_subscriber( $patron );
577 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
580 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
581 qr|Fake send_or_die|,
582 "SendAlerts is using the mocked send_or_die routine";
584 is($err2, 1, "Successfully sent serial notification");
585 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
586 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
588 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
592 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
593 qr|Fake send_or_die|,
594 "SendAlerts is using the mocked send_or_die routine";
595 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
597 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
598 $mocked_koha_email->mock( 'send_or_die', sub {
599 Email::Sender::Failure->throw('something went wrong');
603 $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
604 qr{something went wrong},
605 'Warning is printed';
607 is($err->{error}, 'something went wrong', "Send exception, error message returned");
610 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
612 subtest 'SendAlerts - claimissue' => sub {
617 $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>>');});
619 my $bookseller = Koha::Acquisition::Bookseller->new(
622 address1 => "bookseller's address",
628 my $booksellerid = $bookseller->id;
630 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
632 my $bib = MARC::Record->new();
633 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
635 MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
636 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
640 MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
641 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
644 my ($biblionumber) = AddBiblio($bib, '');
646 $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
647 my $subscriptionid = NewSubscription(
648 undef, "", $booksellerid, undef, undef, $biblionumber,
649 '2013-01-01', 1, undef, undef, undef,
650 undef, undef, undef, undef, undef, undef,
651 1, 'public',undef, '2013-01-01', undef, 1,
652 undef, undef, 0, 'internal', 0,
653 undef, undef, 0, undef, '2013-12-31', 0
656 my ($serials_count, @serials) = GetSerials($subscriptionid);
657 my @serialids = ($serials[0]->{serialid});
661 $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
662 qr/^Bookseller .* without emails at/,
663 "Warn on vendor without email address";
665 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
666 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
668 # Ensure that the preference 'ClaimsLog' is set to logging
669 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
671 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
672 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
674 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
678 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
679 qr|Fake send_or_die|,
680 "SendAlerts is using the mocked send_or_die routine (claimissues)";
681 is( $err, 1, "Successfully sent claim" );
682 is( $email_object->email->header('To'),
683 'testemail@mydomain.com', "mailto correct in sent claim" );
685 $email_object->email->body,
686 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
687 'Serial claim letter for 1 issue constructed successfully'
690 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
691 $mocked_koha_email->mock( 'send_or_die', sub {
692 Email::Sender::Failure->throw('something went wrong');
696 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
697 qr{something went wrong},
698 'Warning is printed';
700 is($err->{error}, 'something went wrong', "Send exception, error message returned");
704 my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
705 my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
706 ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
707 ($serials_count, @serials) = GetSerials($subscriptionid);
708 push @serialids, ($serials[1]->{serialid});
710 warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
711 qr|Fake send_or_die|,
712 "SendAlerts is using the mocked send_or_die routine (claimissues)";
715 $email_object->email->body,
716 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
717 . $email_object->email->crlf
718 . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
719 "Serial claim letter for 2 issues constructed successfully"
722 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
724 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
725 qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
726 "GetPreparedLetter warns about missing notice template";
727 is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
732 subtest 'GetPreparedLetter' => sub {
735 Koha::Notice::Template->new(
740 message_transport_type => 'email'
745 $letter = C4::Letters::GetPreparedLetter(
747 letter_code => 'test',
750 qr{^ERROR: nothing to substitute},
751 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
753 'No letter should be returned by GetPreparedLetter if something went wrong'
757 $letter = C4::Letters::GetPreparedLetter(
759 letter_code => 'test',
763 qr{^ERROR: nothing to substitute},
764 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
766 'No letter should be returned by GetPreparedLetter if something went wrong'
773 subtest 'TranslateNotices' => sub {
776 t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
780 INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
781 ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
782 ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
785 my $letter = C4::Letters::GetPreparedLetter(
788 letter_code => 'code',
789 message_transport_type => 'email',
790 substitute => $substitute,
795 'GetPreparedLetter should return the default one if the lang parameter is not provided'
798 $letter = C4::Letters::GetPreparedLetter(
801 letter_code => 'code',
802 message_transport_type => 'email',
803 substitute => $substitute,
806 is( $letter->{title}, 'una prueba',
807 'GetPreparedLetter should return the required notice if it exists' );
809 $letter = C4::Letters::GetPreparedLetter(
812 letter_code => 'code',
813 message_transport_type => 'email',
814 substitute => $substitute,
820 'GetPreparedLetter should return the default notice if the one required does not exist'
823 t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
825 $letter = C4::Letters::GetPreparedLetter(
828 letter_code => 'code',
829 message_transport_type => 'email',
830 substitute => $substitute,
833 is( $letter->{title}, 'a test',
834 'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
838 subtest 'Test SMS handling in SendQueuedMessages' => sub {
842 t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
843 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
845 my $patron = Koha::Patrons->find($borrowernumber);
847 INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
848 VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
849 |, undef, $borrowernumber
851 eval { C4::Letters::SendQueuedMessages(); };
852 is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
854 my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
855 $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
856 $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
858 warning_like { C4::Letters::SendQueuedMessages(); }
859 qr|Fake send_or_die|,
860 "SendAlerts is using the mocked send_or_die routine (claimissues)";
862 my $message = $schema->resultset('MessageQueue')->search({
863 borrowernumber => $borrowernumber,
867 is( $message->letter_id, $messages->[0]->{id}, "Message letter_id is set correctly" );
868 is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
870 $message->from_address(),
872 'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
875 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
877 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
879 $message_id = C4::Letters::EnqueueLetter($my_message);
880 warning_like { C4::Letters::SendQueuedMessages(); }
881 qr|Fake send_or_die|,
882 "SendAlerts is using the mocked send_or_die routine (claimissues)";
884 $message = $schema->resultset('MessageQueue')->search({
885 borrowernumber => $borrowernumber,
890 $message->from_address(),
891 'override@example.com',
892 'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
895 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
896 $my_message->{to_address} = 'fixme@kidclamp.iswrong';
897 $message_id = C4::Letters::EnqueueLetter($my_message);
899 my $number_attempted = C4::Letters::SendQueuedMessages({
900 borrowernumber => -1, # -1 still triggers the borrowernumber condition
901 letter_code => 'PASSWORD_RESET',
903 is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
905 warning_like { C4::Letters::SendQueuedMessages(); }
906 qr|Fake send_or_die|,
907 "SendAlerts is using the mocked send_or_die routine (claimissues)";
909 my $sms_message_address = $schema->resultset('MessageQueue')->search({
910 borrowernumber => $borrowernumber,
912 })->next()->to_address();
913 is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
915 # Test using SMS::Send::Test driver that's bundled with SMS::Send
916 t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
918 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
919 C4::Letters::EnqueueLetter($my_message);
920 C4::Letters::SendQueuedMessages();
922 $sms_message_address = $schema->resultset('MessageQueue')->search({
923 borrowernumber => $borrowernumber,
925 })->next()->to_address();
926 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' );
930 subtest 'get_item_content' => sub {
933 t::lib::Mocks::mock_preference('dateformat', 'metric');
934 t::lib::Mocks::mock_preference('timeformat', '24hr');
936 {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
937 {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
939 my @item_content_fields = qw( date_due title barcode author itemnumber );
942 for my $item ( @items ) {
943 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
946 my $expected_items_content = <<EOF;
947 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
948 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
950 is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
953 $items_content = q||;
954 for my $item ( @items ) {
955 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
958 $expected_items_content = <<EOF;
959 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
960 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
962 is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
965 subtest 'Test where parameter for SendQueuedMessages' => sub {
968 my $dbh = C4::Context->dbh;
970 my $borrowernumber = Koha::Patron->new({
973 categorycode => $patron_category,
974 branchcode => $library->{branchcode},
975 dateofbirth => $date,
976 })->store->borrowernumber;
978 $dbh->do(q|DELETE FROM message_queue|);
981 'content' => 'a message',
982 'metadata' => 'metadata',
983 'code' => 'TEST_MESSAGE',
984 'content_type' => 'text/plain',
985 'title' => 'message title'
987 'borrowernumber' => $borrowernumber,
988 'to_address' => undef,
989 'message_transport_type' => 'sms',
990 'from_address' => 'from@example.com'
994 'content' => 'another message',
995 'metadata' => 'metadata',
996 'code' => 'TEST_MESSAGE',
997 'content_type' => 'text/plain',
998 'title' => 'message title'
1000 'borrowernumber' => $borrowernumber,
1001 'to_address' => undef,
1002 'message_transport_type' => 'sms',
1003 'from_address' => 'from@example.com'
1007 'content' => 'a skipped message',
1008 'metadata' => 'metadata',
1009 'code' => 'TEST_MESSAGE',
1010 'content_type' => 'text/plain',
1011 'title' => 'message title'
1013 'borrowernumber' => $borrowernumber,
1014 'to_address' => undef,
1015 'message_transport_type' => 'sms',
1016 'from_address' => 'from@example.com'
1018 my @id = ( C4::Letters::EnqueueLetter($my_message),
1019 C4::Letters::EnqueueLetter($my_message2),
1020 C4::Letters::EnqueueLetter($my_message3),
1022 C4::Letters::SendQueuedMessages({
1023 # Test scalar/arrayref in parameter too
1024 letter_code => [ 'TEST_MESSAGE', 'SOMETHING_NOT_THERE' ],
1026 where => q{content NOT LIKE '%skipped%'},
1028 is( Koha::Notice::Messages->find( $id[0] )->status, 'failed', 'Processed but failed' );
1029 is( Koha::Notice::Messages->find( $id[1] )->status, 'failed', 'Processed but failed' );
1030 is( Koha::Notice::Messages->find( $id[2] )->status, 'pending', 'Skipped, still pending' );
1033 subtest 'Test limit parameter for SendQueuedMessages' => sub {
1036 my $dbh = C4::Context->dbh;
1038 my $borrowernumber = Koha::Patron->new({
1039 firstname => 'Jane',
1041 categorycode => $patron_category,
1042 branchcode => $library->{branchcode},
1043 dateofbirth => $date,
1044 email => 'shouldnotwork@wrong.net',
1045 })->store->borrowernumber;
1047 $dbh->do(q|DELETE FROM message_queue|);
1050 'content' => 'a message',
1051 'metadata' => 'metadata',
1052 'code' => 'TEST_MESSAGE',
1053 'content_type' => 'text/plain',
1054 'title' => 'message title'
1056 'borrowernumber' => $borrowernumber,
1057 'to_address' => undef,
1058 'message_transport_type' => 'email',
1059 'from_address' => 'from@example.com'
1061 C4::Letters::EnqueueLetter($my_message) for 1..5;
1063 $send_or_die_count = 0; # reset
1065 my $regex = qr|Fake send_or_die|;
1067 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 1 } ) }
1069 "SendQueuedMessages with limit 1";
1070 is( $messages_sent, 1,
1071 'Sent 1 message with limit of 1 and 5 unprocessed messages' );
1074 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 2 } ) }
1075 [ map { $regex } 1..2 ],
1076 "SendQueuedMessages with limit 2";
1077 is( $messages_sent, 2,
1078 'Sent 2 messages with limit of 2 and 4 unprocessed messages' );
1081 $messages_sent = C4::Letters::SendQueuedMessages( { limit => 3 } ) }
1082 [ map { $regex } 1..2 ],
1083 "SendQueuedMessages with limit 3";
1084 is( $messages_sent, 2,
1085 'Sent 2 messages with limit of 3 and 2 unprocessed messages' );
1087 is( $send_or_die_count, 5, '5 messages sent' );
1088 # Mimic correct status in queue for next tests
1089 Koha::Notice::Messages->search({ to_address => { 'LIKE', '%wrong.net' }})->update({ status => 'sent' });
1091 # Now add a few domain limits too, sending 2 more mails to wrongnet, 2 to fake1, 2 to fake2
1092 # Since we already sent 5 to wrong.net, we expect one deferral when limiting to 6
1093 # Similarly we arrange for 1 deferral for fake1, and 2 for fake2
1094 # We set therefore limit to 3 sent messages: we expect 2 sent, 4 deferred (so 6 processed)
1095 t::lib::Mocks::mock_config( 'message_domain_limits', { domain => [
1096 { name => 'wrong.net', limit => 6, unit => '1m' },
1097 { name => 'fake1.domain', limit => 1, unit => '1m' },
1098 { name => 'fake2.domain', limit => 0, unit => '1m' },
1100 C4::Letters::EnqueueLetter($my_message) for 1..2;
1101 $my_message->{to_address} = 'someone@fake1.domain';
1102 C4::Letters::EnqueueLetter($my_message) for 1..2;
1103 $my_message->{to_address} = 'another@fake2.domain';
1104 C4::Letters::EnqueueLetter($my_message) for 1..2;
1105 my $mocked_util = Test::MockModule->new('Koha::Notice::Util');
1106 my $count_exceeds_calls = 0;
1107 $mocked_util->mock( 'exceeds_limit', sub {
1108 $count_exceeds_calls++;
1109 $mocked_util->original('exceeds_limit')->(@_);
1112 $messages_sent = C4::Letters::SendQueuedMessages({ limit => 3 }) }
1113 [ qr/wrong.net reached limit/, $regex, qr/fake1.domain reached limit/, $regex ],
1114 "SendQueuedMessages with limit 2 and domain limits";
1115 is( $messages_sent, 2, 'Only expecting 2 sent messages' );
1116 is( Koha::Notice::Messages->search({ status => 'pending' })->count, 4, 'Still 4 pending' );
1117 is( $count_exceeds_calls, 6, 'We saw 6 messages while checking domain limits: so we deferred 4' );
1120 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1124 my $dbh = C4::Context->dbh;
1126 my $borrowernumber = Koha::Patron->new({
1127 firstname => 'Jane',
1129 categorycode => $patron_category,
1130 branchcode => $library->{branchcode},
1131 dateofbirth => $date,
1132 smsalertnumber => undef,
1133 })->store->borrowernumber;
1135 $dbh->do(q|DELETE FROM message_queue|);
1138 'content' => 'a message',
1139 'metadata' => 'metadata',
1140 'code' => 'TEST_MESSAGE',
1141 'content_type' => 'text/plain',
1142 'title' => 'message title'
1144 'borrowernumber' => $borrowernumber,
1145 'to_address' => 'to@example.org',
1146 'message_transport_type' => 'email',
1147 'from_address' => '@example.com' # invalid from_address
1149 my $message_id = C4::Letters::EnqueueLetter($my_message);
1150 $send_or_die_count = 0; # reset
1151 my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1152 is( $send_or_die_count, 0, 'Nothing sent when one message_id passed' );
1153 my $message_1 = C4::Letters::GetMessage($message_id);
1154 is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1155 is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1157 $my_message->{from_address} = 'root@example.org'; # valid from_address
1158 $message_id = C4::Letters::EnqueueLetter($my_message);
1159 warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1160 qr|Fake send_or_die|,
1161 "SendQueuedMessages is using the mocked send_or_die routine";
1162 is( $send_or_die_count, 1, 'One message passed through' );
1163 $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1164 my $message_2 = C4::Letters::GetMessage($message_id);
1165 is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1166 is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );