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 => 89;
26 use Email::Sender::Failure;
32 my ( $email_object, $sendmail_params );
34 my $email_sender_module = Test::MockModule->new('Email::Stuffer');
35 $email_sender_module->mock(
38 ( $email_object, $sendmail_params ) = @_;
39 my $str = $email_object->email->as_string;
40 unlike $str, qr/I =C3=A2=C2=99=C2=A5 Koha=/, "Content is not double encoded";
41 warn "Fake send_or_die";
45 use_ok('C4::Context');
46 use_ok('C4::Members');
47 use_ok('C4::Acquisition', qw( NewBasket ));
48 use_ok('C4::Biblio', qw( AddBiblio GetBiblioData ));
49 use_ok('C4::Letters', qw( GetMessageTransportTypes GetMessage EnqueueLetter GetQueuedMessages SendQueuedMessages ResendMessage GetLetters GetPreparedLetter SendAlerts ));
51 use t::lib::TestBuilder;
53 use Koha::DateUtils qw( dt_from_string output_pref );
54 use Koha::Acquisition::Booksellers;
55 use Koha::Acquisition::Bookseller::Contacts;
56 use Koha::Acquisition::Orders;
58 use Koha::Notice::Templates;
60 use Koha::Subscriptions;
61 my $schema = Koha::Database->schema;
62 $schema->storage->txn_begin();
64 my $builder = t::lib::TestBuilder->new;
65 my $dbh = C4::Context->dbh;
67 $dbh->do(q|DELETE FROM letter|);
68 $dbh->do(q|DELETE FROM message_queue|);
69 $dbh->do(q|DELETE FROM message_transport_types|);
71 my $library = $builder->build({
74 branchemail => 'branchemail@address.com',
75 branchreplyto => 'branchreplyto@address.com',
76 branchreturnpath => 'branchreturnpath@address.com',
79 my $patron_category = $builder->build({ source => 'Category' })->{categorycode};
80 my $date = dt_from_string;
81 my $borrowernumber = Koha::Patron->new({
84 categorycode => $patron_category,
85 branchcode => $library->{branchcode},
87 smsalertnumber => undef,
88 })->store->borrowernumber;
90 my $marc_record = MARC::Record->new;
91 my( $biblionumber, $biblioitemnumber ) = AddBiblio( $marc_record, '' );
95 # GetMessageTransportTypes
96 my $mtts = C4::Letters::GetMessageTransportTypes();
97 is( @$mtts, 0, 'GetMessageTransportTypes returns the correct number of message types' );
100 INSERT INTO message_transport_types( message_transport_type ) VALUES ('email'), ('phone'), ('print'), ('sms')
102 $mtts = C4::Letters::GetMessageTransportTypes();
103 is_deeply( $mtts, ['email', 'phone', 'print', 'sms'], 'GetMessageTransportTypes returns all values' );
107 is( C4::Letters::EnqueueLetter(), undef, 'EnqueueLetter without argument returns undef' );
110 borrowernumber => $borrowernumber,
111 message_transport_type => 'sms',
113 from_address => 'from@example.com',
115 my $message_id = C4::Letters::EnqueueLetter($my_message);
116 is( $message_id, undef, 'EnqueueLetter without the letter argument returns undef' );
118 delete $my_message->{message_transport_type};
119 $my_message->{letter} = {
120 content => 'I ♥ Koha',
121 title => '啤酒 is great',
122 metadata => 'metadata',
123 code => 'TEST_MESSAGE',
124 content_type => 'text/plain',
127 $message_id = C4::Letters::EnqueueLetter($my_message);
128 is( $message_id, undef, 'EnqueueLetter without the message type argument argument returns undef' );
130 $my_message->{message_transport_type} = 'sms';
131 $message_id = C4::Letters::EnqueueLetter($my_message);
132 ok(defined $message_id && $message_id > 0, 'new message successfully queued');
136 my $messages = C4::Letters::GetQueuedMessages();
137 is( @$messages, 1, 'GetQueuedMessages without argument returns all the entries' );
139 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
140 is( @$messages, 1, 'one message stored for the borrower' );
141 is( $messages->[0]->{message_id}, $message_id, 'EnqueueLetter returns the message id correctly' );
142 is( $messages->[0]->{borrowernumber}, $borrowernumber, 'EnqueueLetter stores the borrower number correctly' );
143 is( $messages->[0]->{subject}, $my_message->{letter}->{title}, 'EnqueueLetter stores the subject correctly' );
144 is( $messages->[0]->{content}, $my_message->{letter}->{content}, 'EnqueueLetter stores the content correctly' );
145 is( $messages->[0]->{message_transport_type}, $my_message->{message_transport_type}, 'EnqueueLetter stores the message type correctly' );
146 is( $messages->[0]->{status}, 'pending', 'EnqueueLetter stores the status pending correctly' );
147 isnt( $messages->[0]->{time_queued}, undef, 'Time queued inserted by default in message_queue table' );
148 is( $messages->[0]->{updated_on}, $messages->[0]->{time_queued}, 'Time status changed equals time queued when created in message_queue table' );
149 is( $messages->[0]->{failure_code}, '', 'Failure code for successful message correctly empty');
151 # Setting time_queued to something else than now
152 my $yesterday = dt_from_string->subtract( days => 1 );
153 Koha::Notice::Messages->find($messages->[0]->{message_id})->time_queued($yesterday)->store;
156 my $messages_processed = C4::Letters::SendQueuedMessages( { type => 'email' });
157 is($messages_processed, 0, 'No queued messages processed if type limit passed with unused type');
158 $messages_processed = C4::Letters::SendQueuedMessages( { type => 'sms' });
159 is($messages_processed, 1, 'All queued messages processed, found correct number of messages with type limit');
160 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
162 $messages->[0]->{status},
164 'message marked failed if tried to send SMS message for borrower with no smsalertnumber set (bug 11208)'
167 $messages->[0]->{failure_code},
169 'Correct failure code set for borrower with no smsalertnumber set'
171 isnt($messages->[0]->{updated_on}, $messages->[0]->{time_queued}, 'Time status changed differs from time queued when status changes' );
172 is(dt_from_string($messages->[0]->{time_queued}), $yesterday, 'Time queued remaines inmutable' );
175 my $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
176 my $message = C4::Letters::GetMessage( $messages->[0]->{message_id});
177 is( $resent, 1, 'The message should have been resent' );
178 is($message->{status},'pending', 'ResendMessage sets status to pending correctly (bug 12426)');
179 $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
180 is( $resent, 0, 'The message should not have been resent again' );
181 $resent = C4::Letters::ResendMessage();
182 is( $resent, undef, 'ResendMessage should return undef if not message_id given' );
185 is( $messages->[0]->{failure_code}, 'MISSING_SMS', 'Failure code set correctly for no smsalertnumber correctly set' );
188 my $letters = C4::Letters::GetLetters();
189 is( @$letters, 0, 'GetLetters returns the correct number of letters' );
191 my $title = q|<<branches.branchname>> - <<status>>|;
192 my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>,
193 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.
195 <<branches.branchname>>
196 <<branches.branchaddress1>>
199 The following item(s) is/are currently <<status>>:
201 <item> <<count>>. <<items.itemcallnumber>>, Barcode: <<items.barcode>> </item>
203 Thank-you for your prompt attention to this matter.
204 Don't forget your date of birth: <<borrowers.dateofbirth>>.
205 Look at this wonderful biblio timestamp: <<biblio.timestamp>>.
208 $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 );
209 $letters = C4::Letters::GetLetters();
210 is( @$letters, 1, 'GetLetters returns the correct number of letters' );
211 is( $letters->[0]->{module}, 'my module', 'GetLetters gets the module correctly' );
212 is( $letters->[0]->{code}, 'my code', 'GetLetters gets the code correctly' );
213 is( $letters->[0]->{name}, 'my name', 'GetLetters gets the name correctly' );
215 my $data_1 = { module => 'blah', code => 'ISBN', name => 'book' };
216 my $data_2 = { module => 'blah', code => 'ISSN' };
217 $builder->build_object( { class => 'Koha::Notice::Templates', value => $data_1 } );
218 $builder->build_object( { class => 'Koha::Notice::Templates', value => $data_2 } );
220 $letters = GetLetters( { module => 'blah' } );
221 is( scalar(@$letters), 2, 'GetLetters returns the 2 inserted letters' );
223 my ($ISBN_letter) = grep { $_->{code} eq 'ISBN' } @$letters;
224 is( $ISBN_letter->{name}, 'book', 'letter name for "ISBN" letter is book' );
227 t::lib::Mocks::mock_preference('OPACBaseURL', 'http://thisisatest.com');
228 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
230 my $sms_content = 'This is a SMS for an <<status>>';
231 $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 );
234 borrowers => $borrowernumber,
235 branches => $library->{branchcode},
236 biblio => $biblionumber,
243 itemcallnumber => 'my callnumber1',
247 itemcallnumber => 'my callnumber2',
251 my $prepared_letter = GetPreparedLetter((
252 module => 'my module',
253 branchcode => $library->{branchcode},
254 letter_code => 'my code',
256 substitute => $substitute,
259 my $retrieved_library = Koha::Libraries->find($library->{branchcode});
260 my $my_title_letter = $retrieved_library->branchname . qq| - $substitute->{status}|;
261 my $biblio_timestamp = dt_from_string( GetBiblioData($biblionumber)->{timestamp} );
262 my $my_content_letter = qq|Dear Jane Smith,
263 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.
265 |.$retrieved_library->branchname.qq|
266 |.$retrieved_library->branchaddress1.qq|
267 URL: http://thisisatest.com
269 The following item(s) is/are currently $substitute->{status}:
271 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
272 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
274 Thank-you for your prompt attention to this matter.
275 Don't forget your date of birth: | . output_pref({ dt => $date, dateonly => 1 }) . q|.
276 Look at this wonderful biblio timestamp: | . output_pref({ dt => $biblio_timestamp }) . ".\n";
278 is( $prepared_letter->{title}, $my_title_letter, 'GetPreparedLetter returns the title correctly' );
279 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
281 $prepared_letter = GetPreparedLetter((
282 module => 'my module',
283 branchcode => $library->{branchcode},
284 letter_code => 'my code',
286 substitute => $substitute,
288 message_transport_type => 'sms',
290 $my_content_letter = qq|This is a SMS for an $substitute->{status}|;
291 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
294 $prepared_letter = GetPreparedLetter((
295 module => 'my module',
296 branchcode => $library->{branchcode},
297 letter_code => 'my code',
299 substitute => { status => undef },
301 message_transport_type => 'sms',
304 undef, "No warning if GetPreparedLetter called with substitute containing undefined value";
305 is( $prepared_letter->{content}, q|This is a SMS for an |, 'GetPreparedLetter returns the content correctly when substitute contains undefined value' );
307 $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>>.');});
308 $prepared_letter = GetPreparedLetter((
309 module => 'test_date',
311 letter_code => 'test_date',
313 substitute => $substitute,
316 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
318 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp | dateonly>>.' WHERE code = 'test_date';});
319 $prepared_letter = GetPreparedLetter((
320 module => 'test_date',
322 letter_code => 'test_date',
324 substitute => $substitute,
327 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
329 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp|dateonly >>.' WHERE code = 'test_date';});
330 $prepared_letter = GetPreparedLetter((
331 module => 'test_date',
333 letter_code => 'test_date',
335 substitute => $substitute,
338 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
340 t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
341 my $yesterday_night = $date->clone->add( days => -1 )->set_hour(22);
342 $dbh->do(q|UPDATE biblio SET timestamp = ? WHERE biblionumber = ?|, undef, $yesterday_night, $biblionumber );
343 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp>>.' WHERE code = 'test_date';});
344 $prepared_letter = GetPreparedLetter((
345 module => 'test_date',
347 letter_code => 'test_date',
349 substitute => $substitute,
352 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
354 $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>');});
355 $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 %]');});
357 my $testacqorder2_content = <<EOF;
359 [% bookseller.name %]
360 [% FOREACH order IN orders %]
361 Ordernumber [% order.ordernumber %] [% order.quantity %] [% order.listprice | \$Price %]
365 $dbh->do("INSERT INTO letter (module, code, name, title, content) VALUES ('orderacquisition','TESTACQORDER2','Acquisition Order','Order','$testacqorder2_content');");
367 my $popito = $builder->build({
368 source => 'Aqbookseller',
369 value => { name => 'Popito' }
372 my $order_1 = $builder->build({
380 my $order_2 = $builder->build({
388 $prepared_letter = GetPreparedLetter((
389 module => 'orderacquisition',
391 letter_code => 'TESTACQORDER2',
392 tables => { 'aqbooksellers' => $popito->{id} },
394 aqorders => [ $order_1->{ordernumber}, $order_2->{ordernumber} ]
398 my $testacqorder2_expected = qq|Popito
400 Ordernumber | . $order_1->{ordernumber} . qq| 2 12.00
402 Ordernumber | . $order_2->{ordernumber} . qq| 1 23.50
406 is($prepared_letter->{content}, $testacqorder2_expected);
408 # Test that _parseletter doesn't modify its parameters bug 15429
410 my $values = { dateexpiry => '2015-12-13', };
411 C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
412 is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
415 # Correctly format dateexpiry
417 my $values = { dateexpiry => '2015-12-13', };
419 t::lib::Mocks::mock_preference('dateformat', 'metric');
420 t::lib::Mocks::mock_preference('timeformat', '24hr');
421 my $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
422 is( $letter->{content}, 'expiry on 13/12/2015' );
424 t::lib::Mocks::mock_preference('dateformat', 'metric');
425 t::lib::Mocks::mock_preference('timeformat', '12hr');
426 $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
427 is( $letter->{content}, 'expiry on 13/12/2015' );
430 my $bookseller = Koha::Acquisition::Bookseller->new(
433 address1 => "bookseller's address",
439 my $booksellerid = $bookseller->id;
441 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith', phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
442 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
443 my $basketno = NewBasket($booksellerid, 1, 'The basket name');
445 my $budgetid = C4::Budgets::AddBudget({
446 budget_code => "budget_code_test_letters",
447 budget_name => "budget_name_test_letters",
450 my $bib = MARC::Record->new();
451 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
453 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
457 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
461 my $logged_in_user = $builder->build_object(
463 class => 'Koha::Patrons',
465 branchcode => $library->{branchcode},
466 email => 'some@email.com'
471 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
473 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
474 my $order = Koha::Acquisition::Order->new(
476 basketno => $basketno,
478 biblionumber => $biblionumber,
479 budget_id => $budgetid,
482 my $ordernumber = $order->ordernumber;
484 Koha::Acquisition::Baskets->find( $basketno )->close;
487 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
488 qr/^Bookseller .* without emails at/,
489 "SendAlerts prints a warning";
490 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
492 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
493 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
495 # Ensure that the preference 'ClaimsLog' is set to logging
496 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
498 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
499 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
503 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
504 qr|Fake send_or_die|,
505 "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
506 is($err, 1, "Successfully sent order.");
507 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent order");
508 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');
510 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
511 $mocked_koha_email->mock( 'send_or_die', sub {
512 Email::Sender::Failure->throw('something went wrong');
516 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ); }
517 qr{something went wrong},
518 'Warning is printed';
520 is($err->{error}, 'something went wrong', "Send exception, error message returned");
522 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
524 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
525 qr/No orderacquisition TESTACQORDER letter transported by email/,
526 "GetPreparedLetter warns about missing notice template";
527 is($err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined.");
532 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
533 qr|Fake send_or_die|,
534 "SendAlerts is using the mocked send_or_die routine";
536 is($err, 1, "Successfully sent claim");
537 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
538 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
540 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
541 $mocked_koha_email->mock( 'send_or_die', sub {
542 Email::Sender::Failure->throw('something went wrong');
546 $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
547 qr{something went wrong},
548 'Warning is printed';
550 is($err->{error}, 'something went wrong', "Send exception, error message returned");
554 use C4::Serials qw( NewSubscription GetSerials findSerialsByStatus ModSerialStatus );
557 my $internalnotes = 'intnotes';
558 my $number_pattern = $builder->build_object(
560 class => 'Koha::Subscription::Numberpatterns',
561 value => { numberingmethod => 'No. {X}' }
564 my $subscriptionid = NewSubscription(
565 undef, "", undef, undef, undef, $biblionumber,
566 '2013-01-01', 1, undef, undef, undef,
567 undef, undef, undef, undef, undef, undef,
568 1, $notes,undef, '2013-01-01', undef, $number_pattern->id,
569 undef, undef, 0, $internalnotes, 0,
570 undef, undef, 0, undef, '2013-12-31', 0
572 $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>>');});
573 my ($serials_count, @serials) = GetSerials($subscriptionid);
574 my $serial = $serials[0];
576 my $patron = Koha::Patron->new({
579 categorycode => $patron_category,
580 branchcode => $library->{branchcode},
581 dateofbirth => $date,
582 email => 'john.smith@test.de',
584 my $borrowernumber = $patron->borrowernumber;
585 my $subscription = Koha::Subscriptions->find( $subscriptionid );
586 $subscription->add_subscriber( $patron );
588 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
591 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
592 qr|Fake send_or_die|,
593 "SendAlerts is using the mocked send_or_die routine";
595 is($err2, 1, "Successfully sent serial notification");
596 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
597 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
599 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
603 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
604 qr|Fake send_or_die|,
605 "SendAlerts is using the mocked send_or_die routine";
606 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
608 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
609 $mocked_koha_email->mock( 'send_or_die', sub {
610 Email::Sender::Failure->throw('something went wrong');
614 $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
615 qr{something went wrong},
616 'Warning is printed';
618 is($err->{error}, 'something went wrong', "Send exception, error message returned");
621 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
623 subtest '_parseletter' => sub {
626 # Regression test for bug 10843
627 # $dt->add takes a scalar, not undef
629 t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay', undef );
630 $letter = C4::Letters::_parseletter( undef, 'reserves', { waitingdate => "2013-01-01" } );
631 is( ref($letter), 'HASH' );
632 t::lib::Mocks::mock_preference( 'ReservesMaxPickUpDelay', 1 );
633 $letter = C4::Letters::_parseletter( undef, 'reserves', { waitingdate => "2013-01-01" } );
634 is( ref($letter), 'HASH' );
637 subtest 'SendAlerts - claimissue' => sub {
642 $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>>');});
644 my $bookseller = Koha::Acquisition::Bookseller->new(
647 address1 => "bookseller's address",
653 my $booksellerid = $bookseller->id;
655 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
657 my $bib = MARC::Record->new();
658 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
660 MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
661 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
665 MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
666 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
669 my ($biblionumber) = AddBiblio($bib, '');
671 my $number_pattern = $builder->build_object(
673 class => 'Koha::Subscription::Numberpatterns',
674 value => { numberingmethod => 'No. {X}' }
678 my $subscriptionid = NewSubscription(
679 undef, "", $booksellerid, undef, undef, $biblionumber,
680 '2013-01-01', 1, undef, undef, undef,
681 undef, undef, undef, undef, undef, undef,
682 1, 'public',undef, '2013-01-01', undef, $number_pattern->id,
683 undef, undef, 0, 'internal', 0,
684 undef, undef, 0, undef, '2013-12-31', 0
687 my ($serials_count, @serials) = GetSerials($subscriptionid);
688 my @serialids = ($serials[0]->{serialid});
692 $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
693 qr/^Bookseller .* without emails at/,
694 "Warn on vendor without email address";
696 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
697 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
699 # Ensure that the preference 'ClaimsLog' is set to logging
700 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
702 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
703 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
705 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
709 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
710 qr|Fake send_or_die|,
711 "SendAlerts is using the mocked send_or_die routine (claimissues)";
712 is( $err, 1, "Successfully sent claim" );
713 is( $email_object->email->header('To'),
714 'testemail@mydomain.com', "mailto correct in sent claim" );
716 $email_object->email->body,
717 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
718 'Serial claim letter for 1 issue constructed successfully'
721 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
722 $mocked_koha_email->mock( 'send_or_die', sub {
723 Email::Sender::Failure->throw('something went wrong');
727 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
728 qr{something went wrong},
729 'Warning is printed';
731 is($err->{error}, 'something went wrong', "Send exception, error message returned");
735 my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
736 my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
737 ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
738 ($serials_count, @serials) = GetSerials($subscriptionid);
739 push @serialids, ($serials[1]->{serialid});
741 warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
742 qr|Fake send_or_die|,
743 "SendAlerts is using the mocked send_or_die routine (claimissues)";
746 $email_object->email->body,
747 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
748 . $email_object->email->crlf
749 . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
750 "Serial claim letter for 2 issues constructed successfully"
753 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
755 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
756 qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
757 "GetPreparedLetter warns about missing notice template";
758 is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
763 subtest 'GetPreparedLetter' => sub {
766 Koha::Notice::Template->new(
771 message_transport_type => 'email'
776 $letter = C4::Letters::GetPreparedLetter(
778 letter_code => 'test',
781 qr{^ERROR: nothing to substitute},
782 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
784 'No letter should be returned by GetPreparedLetter if something went wrong'
788 $letter = C4::Letters::GetPreparedLetter(
790 letter_code => 'test',
794 qr{^ERROR: nothing to substitute},
795 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
797 'No letter should be returned by GetPreparedLetter if something went wrong'
804 subtest 'TranslateNotices' => sub {
807 t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
811 INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
812 ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
813 ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
816 my $letter = C4::Letters::GetPreparedLetter(
819 letter_code => 'code',
820 message_transport_type => 'email',
821 substitute => $substitute,
826 'GetPreparedLetter should return the default one if the lang parameter is not provided'
829 $letter = C4::Letters::GetPreparedLetter(
832 letter_code => 'code',
833 message_transport_type => 'email',
834 substitute => $substitute,
837 is( $letter->{title}, 'una prueba',
838 'GetPreparedLetter should return the required notice if it exists' );
840 $letter = C4::Letters::GetPreparedLetter(
843 letter_code => 'code',
844 message_transport_type => 'email',
845 substitute => $substitute,
851 'GetPreparedLetter should return the default notice if the one required does not exist'
854 t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
856 $letter = C4::Letters::GetPreparedLetter(
859 letter_code => 'code',
860 message_transport_type => 'email',
861 substitute => $substitute,
864 is( $letter->{title}, 'a test',
865 'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
869 subtest 'Test SMS handling in SendQueuedMessages' => sub {
873 t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
874 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
876 my $patron = Koha::Patrons->find($borrowernumber);
878 INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
879 VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
880 |, undef, $borrowernumber
882 eval { C4::Letters::SendQueuedMessages(); };
883 is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
885 my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
886 $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
887 $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
889 warning_like { C4::Letters::SendQueuedMessages(); }
890 qr|Fake send_or_die|,
891 "SendAlerts is using the mocked send_or_die routine (claimissues)";
893 my $message = $schema->resultset('MessageQueue')->search({
894 borrowernumber => $borrowernumber,
898 is( $message->letter_id, $messages->[0]->{id}, "Message letter_id is set correctly" );
899 is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
901 $message->from_address(),
903 'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
906 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
908 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
910 $message_id = C4::Letters::EnqueueLetter($my_message);
911 warning_like { C4::Letters::SendQueuedMessages(); }
912 qr|Fake send_or_die|,
913 "SendAlerts is using the mocked send_or_die routine (claimissues)";
915 $message = $schema->resultset('MessageQueue')->search({
916 borrowernumber => $borrowernumber,
921 $message->from_address(),
922 'override@example.com',
923 'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
926 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
927 $my_message->{to_address} = 'fixme@kidclamp.iswrong';
928 $message_id = C4::Letters::EnqueueLetter($my_message);
930 my $number_attempted = C4::Letters::SendQueuedMessages({
931 borrowernumber => -1, # -1 still triggers the borrowernumber condition
932 letter_code => 'PASSWORD_RESET',
934 is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
936 warning_like { C4::Letters::SendQueuedMessages(); }
937 qr|Fake send_or_die|,
938 "SendAlerts is using the mocked send_or_die routine (claimissues)";
940 my $sms_message_address = $schema->resultset('MessageQueue')->search({
941 borrowernumber => $borrowernumber,
943 })->next()->to_address();
944 is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
946 # Test using SMS::Send::Test driver that's bundled with SMS::Send
947 t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
949 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
950 C4::Letters::EnqueueLetter($my_message);
951 C4::Letters::SendQueuedMessages();
953 $sms_message_address = $schema->resultset('MessageQueue')->search({
954 borrowernumber => $borrowernumber,
956 })->next()->to_address();
957 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' );
961 subtest 'get_item_content' => sub {
964 t::lib::Mocks::mock_preference('dateformat', 'metric');
965 t::lib::Mocks::mock_preference('timeformat', '24hr');
967 {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
968 {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
970 my @item_content_fields = qw( date_due title barcode author itemnumber );
973 for my $item ( @items ) {
974 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
977 my $expected_items_content = <<EOF;
978 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
979 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
981 is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
984 $items_content = q||;
985 for my $item ( @items ) {
986 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
989 $expected_items_content = <<EOF;
990 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
991 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
993 is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
996 subtest 'Test limit parameter for SendQueuedMessages' => sub {
999 my $dbh = C4::Context->dbh;
1001 my $borrowernumber = Koha::Patron->new({
1002 firstname => 'Jane',
1004 categorycode => $patron_category,
1005 branchcode => $library->{branchcode},
1006 dateofbirth => $date,
1007 smsalertnumber => undef,
1008 })->store->borrowernumber;
1010 $dbh->do(q|DELETE FROM message_queue|);
1013 'content' => 'a message',
1014 'metadata' => 'metadata',
1015 'code' => 'TEST_MESSAGE',
1016 'content_type' => 'text/plain',
1017 'title' => 'message title'
1019 'borrowernumber' => $borrowernumber,
1020 'to_address' => undef,
1021 'message_transport_type' => 'sms',
1022 'from_address' => 'from@example.com'
1024 C4::Letters::EnqueueLetter($my_message);
1025 C4::Letters::EnqueueLetter($my_message);
1026 C4::Letters::EnqueueLetter($my_message);
1027 C4::Letters::EnqueueLetter($my_message);
1028 C4::Letters::EnqueueLetter($my_message);
1029 my $messages_processed = C4::Letters::SendQueuedMessages( { limit => 1 } );
1030 is( $messages_processed, 1,
1031 'Processed 1 message with limit of 1 and 5 unprocessed messages' );
1032 $messages_processed = C4::Letters::SendQueuedMessages( { limit => 2 } );
1033 is( $messages_processed, 2,
1034 'Processed 2 message with limit of 2 and 4 unprocessed messages' );
1035 $messages_processed = C4::Letters::SendQueuedMessages( { limit => 3 } );
1036 is( $messages_processed, 2,
1037 'Processed 2 message with limit of 3 and 2 unprocessed messages' );
1040 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1044 my $dbh = C4::Context->dbh;
1046 my $borrowernumber = Koha::Patron->new({
1047 firstname => 'Jane',
1049 categorycode => $patron_category,
1050 branchcode => $library->{branchcode},
1051 dateofbirth => $date,
1052 smsalertnumber => undef,
1053 })->store->borrowernumber;
1055 $dbh->do(q|DELETE FROM message_queue|);
1058 'content' => 'a message',
1059 'metadata' => 'metadata',
1060 'code' => 'TEST_MESSAGE',
1061 'content_type' => 'text/plain',
1062 'title' => 'message title'
1064 'borrowernumber' => $borrowernumber,
1065 'to_address' => 'to@example.org',
1066 'message_transport_type' => 'email',
1067 'from_address' => '@example.com' # invalid from_address
1069 my $message_id = C4::Letters::EnqueueLetter($my_message);
1070 my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1071 is( $processed, 1, 'Processed 1 message when one message_id passed' );
1072 my $message_1 = C4::Letters::GetMessage($message_id);
1073 is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1074 is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1076 $my_message->{from_address} = 'root@example.org'; # valid from_address
1077 $message_id = C4::Letters::EnqueueLetter($my_message);
1078 warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1079 qr|Fake send_or_die|,
1080 "SendQueuedMessages is using the mocked send_or_die routine";
1081 $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1082 my $message_2 = C4::Letters::GetMessage($message_id);
1083 is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1084 is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );