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 );
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');
49 use_ok('C4::Letters');
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' );
217 subtest 'getletter' => sub {
219 t::lib::Mocks::mock_preference('IndependentBranches', 0);
220 my $letter = C4::Letters::getletter('my module', 'my code', $library->{branchcode}, 'email');
221 is( $letter->{branchcode}, $library->{branchcode}, 'GetLetters gets the branch code correctly' );
222 is( $letter->{module}, 'my module', 'GetLetters gets the module correctly' );
223 is( $letter->{code}, 'my code', 'GetLetters gets the code correctly' );
224 is( $letter->{name}, 'my name', 'GetLetters gets the name correctly' );
225 is( $letter->{is_html}, 1, 'GetLetters gets the boolean is_html correctly' );
226 is( $letter->{title}, $title, 'GetLetters gets the title correctly' );
227 is( $letter->{content}, $content, 'GetLetters gets the content correctly' );
228 is( $letter->{message_transport_type}, 'email', 'GetLetters gets the message type correctly' );
230 t::lib::Mocks::mock_userenv({ branchcode => "anotherlib", flags => 1 });
232 t::lib::Mocks::mock_preference('IndependentBranches', 1);
233 $letter = C4::Letters::getletter('my module', 'my code', $library->{branchcode}, 'email');
234 is( $letter->{branchcode}, $library->{branchcode}, 'GetLetters gets the branch code correctly' );
235 is( $letter->{module}, 'my module', 'GetLetters gets the module correctly' );
236 is( $letter->{code}, 'my code', 'GetLetters gets the code correctly' );
237 is( $letter->{name}, 'my name', 'GetLetters gets the name correctly' );
238 is( $letter->{is_html}, 1, 'GetLetters gets the boolean is_html correctly' );
239 is( $letter->{title}, $title, 'GetLetters gets the title correctly' );
240 is( $letter->{content}, $content, 'GetLetters gets the content correctly' );
241 is( $letter->{message_transport_type}, 'email', 'GetLetters gets the message type correctly' );
246 # Regression test for Bug 14206
247 $dbh->do( q|INSERT INTO letter(branchcode,module,code,name,is_html,title,content,message_transport_type) VALUES ('FFL','my module','my code','my name',1,?,?,'print')|, undef, $title, $content );
248 my $letter14206_a = C4::Letters::getletter('my module', 'my code', 'FFL' );
249 is( $letter14206_a->{message_transport_type}, 'print', 'Bug 14206 - message_transport_type not passed, correct mtt detected' );
250 my $letter14206_b = C4::Letters::getletter('my module', 'my code', 'FFL', 'print');
251 is( $letter14206_b->{message_transport_type}, 'print', 'Bug 14206 - message_transport_type passed, correct mtt detected' );
253 # test for overdue_notices.pl
254 my $overdue_rules = {
255 letter1 => 'my code',
258 my $branchcode = 'FFL';
259 my $letter14206_c = C4::Letters::getletter('my module', $overdue_rules->{"letter$i"}, $branchcode);
260 is( $letter14206_c->{message_transport_type}, 'print', 'Bug 14206 - correct mtt detected for call from overdue_notices.pl' );
263 t::lib::Mocks::mock_preference('OPACBaseURL', 'http://thisisatest.com');
264 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
266 my $sms_content = 'This is a SMS for an <<status>>';
267 $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 );
270 borrowers => $borrowernumber,
271 branches => $library->{branchcode},
272 biblio => $biblionumber,
279 itemcallnumber => 'my callnumber1',
283 itemcallnumber => 'my callnumber2',
287 my $prepared_letter = GetPreparedLetter((
288 module => 'my module',
289 branchcode => $library->{branchcode},
290 letter_code => 'my code',
292 substitute => $substitute,
295 my $retrieved_library = Koha::Libraries->find($library->{branchcode});
296 my $my_title_letter = $retrieved_library->branchname . qq| - $substitute->{status}|;
297 my $biblio_timestamp = dt_from_string( GetBiblioData($biblionumber)->{timestamp} );
298 my $my_content_letter = qq|Dear Jane Smith,
299 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.
301 |.$retrieved_library->branchname.qq|
302 |.$retrieved_library->branchaddress1.qq|
303 URL: http://thisisatest.com
305 The following item(s) is/are currently $substitute->{status}:
307 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
308 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
310 Thank-you for your prompt attention to this matter.
311 Don't forget your date of birth: | . output_pref({ dt => $date, dateonly => 1 }) . q|.
312 Look at this wonderful biblio timestamp: | . output_pref({ dt => $biblio_timestamp }) . ".\n";
314 is( $prepared_letter->{title}, $my_title_letter, 'GetPreparedLetter returns the title correctly' );
315 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
317 $prepared_letter = GetPreparedLetter((
318 module => 'my module',
319 branchcode => $library->{branchcode},
320 letter_code => 'my code',
322 substitute => $substitute,
324 message_transport_type => 'sms',
326 $my_content_letter = qq|This is a SMS for an $substitute->{status}|;
327 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
329 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('test_date','TEST_DATE','Test dates','A title with a timestamp: <<biblio.timestamp>>','This one only contains the date: <<biblio.timestamp | dateonly>>.');});
330 $prepared_letter = GetPreparedLetter((
331 module => 'test_date',
333 letter_code => 'test_date',
335 substitute => $substitute,
338 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
340 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp | dateonly>>.' WHERE code = 'test_date';});
341 $prepared_letter = GetPreparedLetter((
342 module => 'test_date',
344 letter_code => 'test_date',
346 substitute => $substitute,
349 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
351 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp|dateonly >>.' WHERE code = 'test_date';});
352 $prepared_letter = GetPreparedLetter((
353 module => 'test_date',
355 letter_code => 'test_date',
357 substitute => $substitute,
360 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
362 t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
363 my $yesterday_night = $date->clone->add( days => -1 )->set_hour(22);
364 $dbh->do(q|UPDATE biblio SET timestamp = ? WHERE biblionumber = ?|, undef, $yesterday_night, $biblionumber );
365 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp>>.' WHERE code = 'test_date';});
366 $prepared_letter = GetPreparedLetter((
367 module => 'test_date',
369 letter_code => 'test_date',
371 substitute => $substitute,
374 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
376 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('claimacquisition','TESTACQCLAIM','Acquisition Claim','Item Not Received','<<aqbooksellers.name>>|<<aqcontacts.name>>|<order>Ordernumber <<aqorders.ordernumber>> (<<biblio.title>>) (<<aqorders.quantity>> ordered)</order>');});
377 $dbh->do(q{INSERT INTO letter (module, code, name, title, content) VALUES ('orderacquisition','TESTACQORDER','Acquisition Order','Order','<<aqbooksellers.name>>|<<aqcontacts.name>>|<order>Ordernumber <<aqorders.ordernumber>> (<<biblio.title>>) (<<aqorders.quantity>> ordered)</order>');});
379 # Test that _parseletter doesn't modify its parameters bug 15429
381 my $values = { dateexpiry => '2015-12-13', };
382 C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
383 is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
386 # Correctly format dateexpiry
388 my $values = { dateexpiry => '2015-12-13', };
390 t::lib::Mocks::mock_preference('dateformat', 'metric');
391 t::lib::Mocks::mock_preference('timeformat', '24hr');
392 my $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
393 is( $letter->{content}, 'expiry on 13/12/2015' );
395 t::lib::Mocks::mock_preference('dateformat', 'metric');
396 t::lib::Mocks::mock_preference('timeformat', '12hr');
397 $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
398 is( $letter->{content}, 'expiry on 13/12/2015' );
401 my $bookseller = Koha::Acquisition::Bookseller->new(
404 address1 => "bookseller's address",
410 my $booksellerid = $bookseller->id;
412 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith', phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
413 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
414 my $basketno = NewBasket($booksellerid, 1);
416 my $budgetid = C4::Budgets::AddBudget({
417 budget_code => "budget_code_test_letters",
418 budget_name => "budget_name_test_letters",
421 my $bib = MARC::Record->new();
422 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
424 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
428 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
432 my $logged_in_user = $builder->build_object(
434 class => 'Koha::Patrons',
436 branchcode => $library->{branchcode},
437 email => 'some@email.com'
442 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
444 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
445 my $order = Koha::Acquisition::Order->new(
447 basketno => $basketno,
449 biblionumber => $biblionumber,
450 budget_id => $budgetid,
453 my $ordernumber = $order->ordernumber;
455 Koha::Acquisition::Baskets->find( $basketno )->close;
458 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
459 qr/^Bookseller .* without emails at/,
460 "SendAlerts prints a warning";
461 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
463 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
464 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
466 # Ensure that the preference 'ClaimsLog' is set to logging
467 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
469 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
470 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
474 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
475 qr|Fake send_or_die|,
476 "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
477 is($err, 1, "Successfully sent order.");
478 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent order");
479 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Order notice text constructed successfully');
481 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
482 $mocked_koha_email->mock( 'send_or_die', sub {
483 Email::Sender::Failure->throw('something went wrong');
487 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ); }
488 qr{something went wrong},
489 'Warning is printed';
491 is($err->{error}, 'something went wrong', "Send exception, error message returned");
493 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
495 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
496 qr/No orderacquisition TESTACQORDER letter transported by email/,
497 "GetPreparedLetter warns about missing notice template";
498 is($err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined.");
503 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
504 qr|Fake send_or_die|,
505 "SendAlerts is using the mocked send_or_die routine";
507 is($err, 1, "Successfully sent claim");
508 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
509 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
511 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
512 $mocked_koha_email->mock( 'send_or_die', sub {
513 Email::Sender::Failure->throw('something went wrong');
517 $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
518 qr{something went wrong},
519 'Warning is printed';
521 is($err->{error}, 'something went wrong', "Send exception, error message returned");
528 my $internalnotes = 'intnotes';
529 $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
530 my $subscriptionid = NewSubscription(
531 undef, "", undef, undef, undef, $biblionumber,
532 '2013-01-01', 1, undef, undef, undef,
533 undef, undef, undef, undef, undef, undef,
534 1, $notes,undef, '2013-01-01', undef, 1,
535 undef, undef, 0, $internalnotes, 0,
536 undef, undef, 0, undef, '2013-12-31', 0
538 $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>>');});
539 my ($serials_count, @serials) = GetSerials($subscriptionid);
540 my $serial = $serials[0];
542 my $patron = Koha::Patron->new({
545 categorycode => $patron_category,
546 branchcode => $library->{branchcode},
547 dateofbirth => $date,
548 email => 'john.smith@test.de',
550 my $borrowernumber = $patron->borrowernumber;
551 my $subscription = Koha::Subscriptions->find( $subscriptionid );
552 $subscription->add_subscriber( $patron );
554 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
557 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
558 qr|Fake send_or_die|,
559 "SendAlerts is using the mocked send_or_die routine";
561 is($err2, 1, "Successfully sent serial notification");
562 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
563 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
565 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
569 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
570 qr|Fake send_or_die|,
571 "SendAlerts is using the mocked send_or_die routine";
572 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
574 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
575 $mocked_koha_email->mock( 'send_or_die', sub {
576 Email::Sender::Failure->throw('something went wrong');
580 $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
581 qr{something went wrong},
582 'Warning is printed';
584 is($err->{error}, 'something went wrong', "Send exception, error message returned");
587 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
589 subtest 'SendAlerts - claimissue' => sub {
594 $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>>');});
596 my $bookseller = Koha::Acquisition::Bookseller->new(
599 address1 => "bookseller's address",
605 my $booksellerid = $bookseller->id;
607 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
609 my $bib = MARC::Record->new();
610 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
612 MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
613 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
617 MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
618 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
621 my ($biblionumber) = AddBiblio($bib, '');
623 $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
624 my $subscriptionid = NewSubscription(
625 undef, "", $booksellerid, undef, undef, $biblionumber,
626 '2013-01-01', 1, undef, undef, undef,
627 undef, undef, undef, undef, undef, undef,
628 1, 'public',undef, '2013-01-01', undef, 1,
629 undef, undef, 0, 'internal', 0,
630 undef, undef, 0, undef, '2013-12-31', 0
633 my ($serials_count, @serials) = GetSerials($subscriptionid);
634 my @serialids = ($serials[0]->{serialid});
638 $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
639 qr/^Bookseller .* without emails at/,
640 "Warn on vendor without email address";
642 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
643 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
645 # Ensure that the preference 'ClaimsLog' is set to logging
646 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
648 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
649 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
651 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
655 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
656 qr|Fake send_or_die|,
657 "SendAlerts is using the mocked send_or_die routine (claimissues)";
658 is( $err, 1, "Successfully sent claim" );
659 is( $email_object->email->header('To'),
660 'testemail@mydomain.com', "mailto correct in sent claim" );
662 $email_object->email->body,
663 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
664 'Serial claim letter for 1 issue constructed successfully'
667 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
668 $mocked_koha_email->mock( 'send_or_die', sub {
669 Email::Sender::Failure->throw('something went wrong');
673 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
674 qr{something went wrong},
675 'Warning is printed';
677 is($err->{error}, 'something went wrong', "Send exception, error message returned");
681 my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
682 my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
683 ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
684 ($serials_count, @serials) = GetSerials($subscriptionid);
685 push @serialids, ($serials[1]->{serialid});
687 warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
688 qr|Fake send_or_die|,
689 "SendAlerts is using the mocked send_or_die routine (claimissues)";
692 $email_object->email->body,
693 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
694 . $email_object->email->crlf
695 . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
696 "Serial claim letter for 2 issues constructed successfully"
699 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
701 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
702 qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
703 "GetPreparedLetter warns about missing notice template";
704 is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
709 subtest 'GetPreparedLetter' => sub {
712 Koha::Notice::Template->new(
717 message_transport_type => 'email'
722 $letter = C4::Letters::GetPreparedLetter(
724 letter_code => 'test',
727 qr{^ERROR: nothing to substitute},
728 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
730 'No letter should be returned by GetPreparedLetter if something went wrong'
734 $letter = C4::Letters::GetPreparedLetter(
736 letter_code => 'test',
740 qr{^ERROR: nothing to substitute},
741 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
743 'No letter should be returned by GetPreparedLetter if something went wrong'
750 subtest 'TranslateNotices' => sub {
753 t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
757 INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
758 ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
759 ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
762 my $letter = C4::Letters::GetPreparedLetter(
765 letter_code => 'code',
766 message_transport_type => 'email',
767 substitute => $substitute,
772 'GetPreparedLetter should return the default one if the lang parameter is not provided'
775 $letter = C4::Letters::GetPreparedLetter(
778 letter_code => 'code',
779 message_transport_type => 'email',
780 substitute => $substitute,
783 is( $letter->{title}, 'una prueba',
784 'GetPreparedLetter should return the required notice if it exists' );
786 $letter = C4::Letters::GetPreparedLetter(
789 letter_code => 'code',
790 message_transport_type => 'email',
791 substitute => $substitute,
797 'GetPreparedLetter should return the default notice if the one required does not exist'
800 t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
802 $letter = C4::Letters::GetPreparedLetter(
805 letter_code => 'code',
806 message_transport_type => 'email',
807 substitute => $substitute,
810 is( $letter->{title}, 'a test',
811 'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
815 subtest 'SendQueuedMessages' => sub {
819 t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
820 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
822 my $patron = Koha::Patrons->find($borrowernumber);
824 INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
825 VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
826 |, undef, $borrowernumber
828 eval { C4::Letters::SendQueuedMessages(); };
829 is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
831 my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
832 $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
833 $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
835 warning_like { C4::Letters::SendQueuedMessages(); }
836 qr|Fake send_or_die|,
837 "SendAlerts is using the mocked send_or_die routine (claimissues)";
839 my $message = $schema->resultset('MessageQueue')->search({
840 borrowernumber => $borrowernumber,
844 is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
846 $message->from_address(),
848 'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
851 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
853 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
855 $message_id = C4::Letters::EnqueueLetter($my_message);
856 warning_like { C4::Letters::SendQueuedMessages(); }
857 qr|Fake send_or_die|,
858 "SendAlerts is using the mocked send_or_die routine (claimissues)";
860 $message = $schema->resultset('MessageQueue')->search({
861 borrowernumber => $borrowernumber,
866 $message->from_address(),
867 'override@example.com',
868 'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
871 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
872 $my_message->{to_address} = 'fixme@kidclamp.iswrong';
873 $message_id = C4::Letters::EnqueueLetter($my_message);
875 my $number_attempted = C4::Letters::SendQueuedMessages({
876 borrowernumber => -1, # -1 still triggers the borrowernumber condition
877 letter_code => 'PASSWORD_RESET',
879 is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
881 warning_like { C4::Letters::SendQueuedMessages(); }
882 qr|Fake send_or_die|,
883 "SendAlerts is using the mocked send_or_die routine (claimissues)";
885 my $sms_message_address = $schema->resultset('MessageQueue')->search({
886 borrowernumber => $borrowernumber,
888 })->next()->to_address();
889 is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
893 subtest 'get_item_content' => sub {
896 t::lib::Mocks::mock_preference('dateformat', 'metric');
897 t::lib::Mocks::mock_preference('timeformat', '24hr');
899 {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
900 {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
902 my @item_content_fields = qw( date_due title barcode author itemnumber );
905 for my $item ( @items ) {
906 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
909 my $expected_items_content = <<EOF;
910 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
911 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
913 is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
916 $items_content = q||;
917 for my $item ( @items ) {
918 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
921 $expected_items_content = <<EOF;
922 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
923 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
925 is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
928 subtest 'Test limit parameter for SendQueuedMessages' => sub {
931 my $dbh = C4::Context->dbh;
933 my $borrowernumber = Koha::Patron->new({
936 categorycode => $patron_category,
937 branchcode => $library->{branchcode},
938 dateofbirth => $date,
939 smsalertnumber => undef,
940 })->store->borrowernumber;
942 $dbh->do(q|DELETE FROM message_queue|);
945 'content' => 'a message',
946 'metadata' => 'metadata',
947 'code' => 'TEST_MESSAGE',
948 'content_type' => 'text/plain',
949 'title' => 'message title'
951 'borrowernumber' => $borrowernumber,
952 'to_address' => undef,
953 'message_transport_type' => 'sms',
954 'from_address' => 'from@example.com'
956 C4::Letters::EnqueueLetter($my_message);
957 C4::Letters::EnqueueLetter($my_message);
958 C4::Letters::EnqueueLetter($my_message);
959 C4::Letters::EnqueueLetter($my_message);
960 C4::Letters::EnqueueLetter($my_message);
961 my $messages_processed = C4::Letters::SendQueuedMessages( { limit => 1 } );
962 is( $messages_processed, 1,
963 'Processed 1 message with limit of 1 and 5 unprocessed messages' );
964 $messages_processed = C4::Letters::SendQueuedMessages( { limit => 2 } );
965 is( $messages_processed, 2,
966 'Processed 2 message with limit of 2 and 4 unprocessed messages' );
967 $messages_processed = C4::Letters::SendQueuedMessages( { limit => 3 } );
968 is( $messages_processed, 2,
969 'Processed 2 message with limit of 3 and 2 unprocessed messages' );
972 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
976 my $dbh = C4::Context->dbh;
978 my $borrowernumber = Koha::Patron->new({
981 categorycode => $patron_category,
982 branchcode => $library->{branchcode},
983 dateofbirth => $date,
984 smsalertnumber => undef,
985 })->store->borrowernumber;
987 $dbh->do(q|DELETE FROM message_queue|);
990 'content' => 'a message',
991 'metadata' => 'metadata',
992 'code' => 'TEST_MESSAGE',
993 'content_type' => 'text/plain',
994 'title' => 'message title'
996 'borrowernumber' => $borrowernumber,
997 'to_address' => 'to@example.org',
998 'message_transport_type' => 'email',
999 'from_address' => 'root@localhost.' # invalid KohaAdminEmailAddress
1001 my $message_id = C4::Letters::EnqueueLetter($my_message);
1003 C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1004 } 'Koha::Exceptions::BadParameter',
1005 'Exception thrown if invalid email is passed';
1006 my $message_1 = C4::Letters::GetMessage($message_id);
1007 # FIXME must be 'failed'
1008 is( $message_1->{status}, 'pending', 'Invalid KohaAdminEmailAddress => status pending' );
1010 $my_message->{from_address} = 'root@example.org'; # valid KohaAdminEmailAddress
1011 $message_id = C4::Letters::EnqueueLetter($my_message);
1012 warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1013 qr|Fake send_or_die|,
1014 "SendQueuedMessages is using the mocked send_or_die routine";
1015 $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1016 my $message_2 = C4::Letters::GetMessage($message_id);
1017 is( $message_1->{status}, 'pending', 'Message 1 status is unchanged' ); # Must be 'failed'
1018 is( $message_2->{status}, 'sent', 'Valid KohaAdminEmailAddress => status sent' );