Bug 25898: Prohibit indirect object notation
[koha.git] / t / db_dependent / Letters.t
blob9a629bdfbfc9722f275e43ed8d75b8f7e24311d5
1 #!/usr/bin/perl
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>.
20 use Modern::Perl;
21 use Test::More tests => 73;
22 use Test::MockModule;
23 use Test::Warn;
25 use MARC::Record;
27 my ( $email_object, $sendmail_params );
29 my $email_sender_module = Test::MockModule->new('Email::Stuffer');
30 $email_sender_module->mock(
31 'send_or_die',
32 sub {
33 ( $email_object, $sendmail_params ) = @_;
34 warn "Fake send_or_die";
38 use_ok('C4::Context');
39 use_ok('C4::Members');
40 use_ok('C4::Acquisition');
41 use_ok('C4::Biblio');
42 use_ok('C4::Letters');
43 use t::lib::Mocks;
44 use t::lib::TestBuilder;
45 use Koha::Database;
46 use Koha::DateUtils qw( dt_from_string output_pref );
47 use Koha::Acquisition::Booksellers;
48 use Koha::Acquisition::Bookseller::Contacts;
49 use Koha::Acquisition::Orders;
50 use Koha::Libraries;
51 use Koha::Notice::Templates;
52 use Koha::Patrons;
53 use Koha::Subscriptions;
54 my $schema = Koha::Database->schema;
55 $schema->storage->txn_begin();
57 my $builder = t::lib::TestBuilder->new;
58 my $dbh = C4::Context->dbh;
60 $dbh->do(q|DELETE FROM letter|);
61 $dbh->do(q|DELETE FROM message_queue|);
62 $dbh->do(q|DELETE FROM message_transport_types|);
64 my $library = $builder->build({
65 source => 'Branch',
66 value => {
67 branchemail => 'branchemail@address.com',
68 branchreplyto => 'branchreplyto@address.com',
69 branchreturnpath => 'branchreturnpath@address.com',
71 });
72 my $patron_category = $builder->build({ source => 'Category' })->{categorycode};
73 my $date = dt_from_string;
74 my $borrowernumber = Koha::Patron->new({
75 firstname => 'Jane',
76 surname => 'Smith',
77 categorycode => $patron_category,
78 branchcode => $library->{branchcode},
79 dateofbirth => $date,
80 smsalertnumber => undef,
81 })->store->borrowernumber;
83 my $marc_record = MARC::Record->new;
84 my( $biblionumber, $biblioitemnumber ) = AddBiblio( $marc_record, '' );
88 # GetMessageTransportTypes
89 my $mtts = C4::Letters::GetMessageTransportTypes();
90 is( @$mtts, 0, 'GetMessageTransportTypes returns the correct number of message types' );
92 $dbh->do(q|
93 INSERT INTO message_transport_types( message_transport_type ) VALUES ('email'), ('phone'), ('print'), ('sms')
94 |);
95 $mtts = C4::Letters::GetMessageTransportTypes();
96 is_deeply( $mtts, ['email', 'phone', 'print', 'sms'], 'GetMessageTransportTypes returns all values' );
99 # EnqueueLetter
100 is( C4::Letters::EnqueueLetter(), undef, 'EnqueueLetter without argument returns undef' );
102 my $my_message = {
103 borrowernumber => $borrowernumber,
104 message_transport_type => 'sms',
105 to_address => undef,
106 from_address => 'from@example.com',
108 my $message_id = C4::Letters::EnqueueLetter($my_message);
109 is( $message_id, undef, 'EnqueueLetter without the letter argument returns undef' );
111 delete $my_message->{message_transport_type};
112 $my_message->{letter} = {
113 content => 'a message',
114 title => 'message title',
115 metadata => 'metadata',
116 code => 'TEST_MESSAGE',
117 content_type => 'text/plain',
120 $message_id = C4::Letters::EnqueueLetter($my_message);
121 is( $message_id, undef, 'EnqueueLetter without the message type argument argument returns undef' );
123 $my_message->{message_transport_type} = 'sms';
124 $message_id = C4::Letters::EnqueueLetter($my_message);
125 ok(defined $message_id && $message_id > 0, 'new message successfully queued');
128 # GetQueuedMessages
129 my $messages = C4::Letters::GetQueuedMessages();
130 is( @$messages, 1, 'GetQueuedMessages without argument returns all the entries' );
132 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
133 is( @$messages, 1, 'one message stored for the borrower' );
134 is( $messages->[0]->{message_id}, $message_id, 'EnqueueLetter returns the message id correctly' );
135 is( $messages->[0]->{borrowernumber}, $borrowernumber, 'EnqueueLetter stores the borrower number correctly' );
136 is( $messages->[0]->{subject}, $my_message->{letter}->{title}, 'EnqueueLetter stores the subject correctly' );
137 is( $messages->[0]->{content}, $my_message->{letter}->{content}, 'EnqueueLetter stores the content correctly' );
138 is( $messages->[0]->{message_transport_type}, $my_message->{message_transport_type}, 'EnqueueLetter stores the message type correctly' );
139 is( $messages->[0]->{status}, 'pending', 'EnqueueLetter stores the status pending correctly' );
140 isnt( $messages->[0]->{time_queued}, undef, 'Time queued inserted by default in message_queue table' );
141 is( $messages->[0]->{updated_on}, $messages->[0]->{time_queued}, 'Time status changed equals time queued when created in message_queue table' );
143 # Setting time_queued to something else than now
144 my $yesterday = dt_from_string->subtract( days => 1 );
145 Koha::Notice::Messages->find($messages->[0]->{message_id})->time_queued($yesterday)->store;
147 # SendQueuedMessages
148 my $messages_processed = C4::Letters::SendQueuedMessages( { type => 'email' });
149 is($messages_processed, 0, 'No queued messages processed if type limit passed with unused type');
150 $messages_processed = C4::Letters::SendQueuedMessages( { type => 'sms' });
151 is($messages_processed, 1, 'All queued messages processed, found correct number of messages with type limit');
152 $messages = C4::Letters::GetQueuedMessages({ borrowernumber => $borrowernumber });
154 $messages->[0]->{status},
155 'failed',
156 'message marked failed if tried to send SMS message for borrower with no smsalertnumber set (bug 11208)'
158 isnt($messages->[0]->{updated_on}, $messages->[0]->{time_queued}, 'Time status changed differs from time queued when status changes' );
159 is(dt_from_string($messages->[0]->{time_queued}), $yesterday, 'Time queued remaines inmutable' );
161 # ResendMessage
162 my $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
163 my $message = C4::Letters::GetMessage( $messages->[0]->{message_id});
164 is( $resent, 1, 'The message should have been resent' );
165 is($message->{status},'pending', 'ResendMessage sets status to pending correctly (bug 12426)');
166 $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
167 is( $resent, 0, 'The message should not have been resent again' );
168 $resent = C4::Letters::ResendMessage();
169 is( $resent, undef, 'ResendMessage should return undef if not message_id given' );
171 # GetLetters
172 my $letters = C4::Letters::GetLetters();
173 is( @$letters, 0, 'GetLetters returns the correct number of letters' );
175 my $title = q|<<branches.branchname>> - <<status>>|;
176 my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>,
177 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.
179 <<branches.branchname>>
180 <<branches.branchaddress1>>
181 URL: <<OPACBaseURL>>
183 The following item(s) is/are currently <<status>>:
185 <item> <<count>>. <<items.itemcallnumber>>, Barcode: <<items.barcode>> </item>
187 Thank-you for your prompt attention to this matter.
188 Don't forget your date of birth: <<borrowers.dateofbirth>>.
189 Look at this wonderful biblio timestamp: <<biblio.timestamp>>.
192 $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 );
193 $letters = C4::Letters::GetLetters();
194 is( @$letters, 1, 'GetLetters returns the correct number of letters' );
195 is( $letters->[0]->{module}, 'my module', 'GetLetters gets the module correctly' );
196 is( $letters->[0]->{code}, 'my code', 'GetLetters gets the code correctly' );
197 is( $letters->[0]->{name}, 'my name', 'GetLetters gets the name correctly' );
200 # getletter
201 subtest 'getletter' => sub {
202 plan tests => 16;
203 t::lib::Mocks::mock_preference('IndependentBranches', 0);
204 my $letter = C4::Letters::getletter('my module', 'my code', $library->{branchcode}, 'email');
205 is( $letter->{branchcode}, $library->{branchcode}, 'GetLetters gets the branch code correctly' );
206 is( $letter->{module}, 'my module', 'GetLetters gets the module correctly' );
207 is( $letter->{code}, 'my code', 'GetLetters gets the code correctly' );
208 is( $letter->{name}, 'my name', 'GetLetters gets the name correctly' );
209 is( $letter->{is_html}, 1, 'GetLetters gets the boolean is_html correctly' );
210 is( $letter->{title}, $title, 'GetLetters gets the title correctly' );
211 is( $letter->{content}, $content, 'GetLetters gets the content correctly' );
212 is( $letter->{message_transport_type}, 'email', 'GetLetters gets the message type correctly' );
214 t::lib::Mocks::mock_userenv({ branchcode => "anotherlib", flags => 1 });
216 t::lib::Mocks::mock_preference('IndependentBranches', 1);
217 $letter = C4::Letters::getletter('my module', 'my code', $library->{branchcode}, 'email');
218 is( $letter->{branchcode}, $library->{branchcode}, 'GetLetters gets the branch code correctly' );
219 is( $letter->{module}, 'my module', 'GetLetters gets the module correctly' );
220 is( $letter->{code}, 'my code', 'GetLetters gets the code correctly' );
221 is( $letter->{name}, 'my name', 'GetLetters gets the name correctly' );
222 is( $letter->{is_html}, 1, 'GetLetters gets the boolean is_html correctly' );
223 is( $letter->{title}, $title, 'GetLetters gets the title correctly' );
224 is( $letter->{content}, $content, 'GetLetters gets the content correctly' );
225 is( $letter->{message_transport_type}, 'email', 'GetLetters gets the message type correctly' );
230 # Regression test for Bug 14206
231 $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 );
232 my $letter14206_a = C4::Letters::getletter('my module', 'my code', 'FFL' );
233 is( $letter14206_a->{message_transport_type}, 'print', 'Bug 14206 - message_transport_type not passed, correct mtt detected' );
234 my $letter14206_b = C4::Letters::getletter('my module', 'my code', 'FFL', 'print');
235 is( $letter14206_b->{message_transport_type}, 'print', 'Bug 14206 - message_transport_type passed, correct mtt detected' );
237 # test for overdue_notices.pl
238 my $overdue_rules = {
239 letter1 => 'my code',
241 my $i = 1;
242 my $branchcode = 'FFL';
243 my $letter14206_c = C4::Letters::getletter('my module', $overdue_rules->{"letter$i"}, $branchcode);
244 is( $letter14206_c->{message_transport_type}, 'print', 'Bug 14206 - correct mtt detected for call from overdue_notices.pl' );
246 # GetPreparedLetter
247 t::lib::Mocks::mock_preference('OPACBaseURL', 'http://thisisatest.com');
248 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
250 my $sms_content = 'This is a SMS for an <<status>>';
251 $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 );
253 my $tables = {
254 borrowers => $borrowernumber,
255 branches => $library->{branchcode},
256 biblio => $biblionumber,
258 my $substitute = {
259 status => 'overdue',
261 my $repeat = [
263 itemcallnumber => 'my callnumber1',
264 barcode => '1234',
267 itemcallnumber => 'my callnumber2',
268 barcode => '5678',
271 my $prepared_letter = GetPreparedLetter((
272 module => 'my module',
273 branchcode => $library->{branchcode},
274 letter_code => 'my code',
275 tables => $tables,
276 substitute => $substitute,
277 repeat => $repeat,
279 my $retrieved_library = Koha::Libraries->find($library->{branchcode});
280 my $my_title_letter = $retrieved_library->branchname . qq| - $substitute->{status}|;
281 my $biblio_timestamp = dt_from_string( GetBiblioData($biblionumber)->{timestamp} );
282 my $my_content_letter = qq|Dear Jane Smith,
283 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.
285 |.$retrieved_library->branchname.qq|
286 |.$retrieved_library->branchaddress1.qq|
287 URL: http://thisisatest.com
289 The following item(s) is/are currently $substitute->{status}:
291 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
292 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
294 Thank-you for your prompt attention to this matter.
295 Don't forget your date of birth: | . output_pref({ dt => $date, dateonly => 1 }) . q|.
296 Look at this wonderful biblio timestamp: | . output_pref({ dt => $biblio_timestamp }) . ".\n";
298 is( $prepared_letter->{title}, $my_title_letter, 'GetPreparedLetter returns the title correctly' );
299 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
301 $prepared_letter = GetPreparedLetter((
302 module => 'my module',
303 branchcode => $library->{branchcode},
304 letter_code => 'my code',
305 tables => $tables,
306 substitute => $substitute,
307 repeat => $repeat,
308 message_transport_type => 'sms',
310 $my_content_letter = qq|This is a SMS for an $substitute->{status}|;
311 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
313 $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>>.');});
314 $prepared_letter = GetPreparedLetter((
315 module => 'test_date',
316 branchcode => '',
317 letter_code => 'test_date',
318 tables => $tables,
319 substitute => $substitute,
320 repeat => $repeat,
322 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
324 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp | dateonly>>.' WHERE code = 'test_date';});
325 $prepared_letter = GetPreparedLetter((
326 module => 'test_date',
327 branchcode => '',
328 letter_code => 'test_date',
329 tables => $tables,
330 substitute => $substitute,
331 repeat => $repeat,
333 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
335 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp|dateonly >>.' WHERE code = 'test_date';});
336 $prepared_letter = GetPreparedLetter((
337 module => 'test_date',
338 branchcode => '',
339 letter_code => 'test_date',
340 tables => $tables,
341 substitute => $substitute,
342 repeat => $repeat,
344 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
346 t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
347 my $yesterday_night = $date->clone->add( days => -1 )->set_hour(22);
348 $dbh->do(q|UPDATE biblio SET timestamp = ? WHERE biblionumber = ?|, undef, $yesterday_night, $biblionumber );
349 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp>>.' WHERE code = 'test_date';});
350 $prepared_letter = GetPreparedLetter((
351 module => 'test_date',
352 branchcode => '',
353 letter_code => 'test_date',
354 tables => $tables,
355 substitute => $substitute,
356 repeat => $repeat,
358 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
360 $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>');});
361 $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>');});
363 # Test that _parseletter doesn't modify its parameters bug 15429
365 my $values = { dateexpiry => '2015-12-13', };
366 C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
367 is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
370 # Correctly format dateexpiry
372 my $values = { dateexpiry => '2015-12-13', };
374 t::lib::Mocks::mock_preference('dateformat', 'metric');
375 t::lib::Mocks::mock_preference('timeformat', '24hr');
376 my $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
377 is( $letter->{content}, 'expiry on 13/12/2015' );
379 t::lib::Mocks::mock_preference('dateformat', 'metric');
380 t::lib::Mocks::mock_preference('timeformat', '12hr');
381 $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
382 is( $letter->{content}, 'expiry on 13/12/2015' );
385 my $bookseller = Koha::Acquisition::Bookseller->new(
387 name => "my vendor",
388 address1 => "bookseller's address",
389 phone => "0123456",
390 active => 1,
391 deliverytime => 5,
393 )->store;
394 my $booksellerid = $bookseller->id;
396 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith', phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
397 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
398 my $basketno = NewBasket($booksellerid, 1);
400 my $budgetid = C4::Budgets::AddBudget({
401 budget_code => "budget_code_test_letters",
402 budget_name => "budget_name_test_letters",
405 my $bib = MARC::Record->new();
406 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
407 $bib->append_fields(
408 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
410 } else {
411 $bib->append_fields(
412 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
416 my $logged_in_user = $builder->build_object(
418 class => 'Koha::Patrons',
419 value => {
420 branchcode => $library->{branchcode},
421 email => 'some@email.com'
426 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
428 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
429 my $order = Koha::Acquisition::Order->new(
431 basketno => $basketno,
432 quantity => 1,
433 biblionumber => $biblionumber,
434 budget_id => $budgetid,
436 )->store;
437 my $ordernumber = $order->ordernumber;
439 C4::Acquisition::CloseBasket( $basketno );
440 my $err;
441 warning_like {
442 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
443 qr/^Bookseller .* without emails at/,
444 "SendAlerts prints a warning";
445 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
447 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
448 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
450 # Ensure that the preference 'LetterLog' is set to logging
451 t::lib::Mocks::mock_preference( 'LetterLog', 'on' );
453 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
454 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
457 warning_like {
458 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
459 qr|Fake send_or_die|,
460 "SendAlerts is using the mocked sendmail routine (orderacquisition)";
461 is($err, 1, "Successfully sent order.");
462 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent order");
463 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Order notice text constructed successfully');
465 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
466 warning_like {
467 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
468 qr/No orderacquisition TESTACQORDER letter transported by email/,
469 "GetPreparedLetter warns about missing notice template";
470 is($err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined.");
474 warning_like {
475 $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
476 qr|Fake send_or_die|,
477 "SendAlerts is using the mocked sendmail routine";
479 is($err, 1, "Successfully sent claim");
480 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
481 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
485 use C4::Serials;
487 my $notes = 'notes';
488 my $internalnotes = 'intnotes';
489 $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
490 my $subscriptionid = NewSubscription(
491 undef, "", undef, undef, undef, $biblionumber,
492 '2013-01-01', 1, undef, undef, undef,
493 undef, undef, undef, undef, undef, undef,
494 1, $notes,undef, '2013-01-01', undef, 1,
495 undef, undef, 0, $internalnotes, 0,
496 undef, undef, 0, undef, '2013-12-31', 0
498 $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>>');});
499 my ($serials_count, @serials) = GetSerials($subscriptionid);
500 my $serial = $serials[0];
502 my $patron = Koha::Patron->new({
503 firstname => 'John',
504 surname => 'Smith',
505 categorycode => $patron_category,
506 branchcode => $library->{branchcode},
507 dateofbirth => $date,
508 email => 'john.smith@test.de',
509 })->store;
510 my $borrowernumber = $patron->borrowernumber;
511 my $subscription = Koha::Subscriptions->find( $subscriptionid );
512 $subscription->add_subscriber( $patron );
514 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
515 my $err2;
516 warning_like {
517 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
518 qr|Fake send_or_die|,
519 "SendAlerts is using the mocked sendmail routine";
521 is($err2, 1, "Successfully sent serial notification");
522 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
523 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
525 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
527 my $err3;
528 warning_like {
529 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
530 qr|Fake send_or_die|,
531 "SendAlerts is using the mocked sendmail routine";
532 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
534 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
536 subtest 'SendAlerts - claimissue' => sub {
537 plan tests => 9;
539 use C4::Serials;
541 $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>>');});
543 my $bookseller = Koha::Acquisition::Bookseller->new(
545 name => "my vendor",
546 address1 => "bookseller's address",
547 phone => "0123456",
548 active => 1,
549 deliverytime => 5,
551 )->store;
552 my $booksellerid = $bookseller->id;
554 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
556 my $bib = MARC::Record->new();
557 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
558 $bib->append_fields(
559 MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
560 MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
562 } else {
563 $bib->append_fields(
564 MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
565 MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
568 my ($biblionumber) = AddBiblio($bib, '');
570 $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
571 my $subscriptionid = NewSubscription(
572 undef, "", $booksellerid, undef, undef, $biblionumber,
573 '2013-01-01', 1, undef, undef, undef,
574 undef, undef, undef, undef, undef, undef,
575 1, 'public',undef, '2013-01-01', undef, 1,
576 undef, undef, 0, 'internal', 0,
577 undef, undef, 0, undef, '2013-12-31', 0
580 my ($serials_count, @serials) = GetSerials($subscriptionid);
581 my @serialids = ($serials[0]->{serialid});
583 my $err;
584 warning_like {
585 $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
586 qr/^Bookseller .* without emails at/,
587 "Warn on vendor without email address";
589 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
590 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
592 # Ensure that the preference 'LetterLog' is set to logging
593 t::lib::Mocks::mock_preference( 'LetterLog', 'on' );
595 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
596 t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
598 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
601 warning_like {
602 $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
603 qr|Fake send_or_die|,
604 "SendAlerts is using the mocked sendmail routine (claimissues)";
605 is( $err, 1, "Successfully sent claim" );
606 is( $email_object->email->header('To'),
607 'testemail@mydomain.com', "mailto correct in sent claim" );
609 $email_object->email->body,
610 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
611 'Serial claim letter for 1 issue constructed successfully'
616 my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
617 my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
618 ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
619 ($serials_count, @serials) = GetSerials($subscriptionid);
620 push @serialids, ($serials[1]->{serialid});
622 warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
623 qr|Fake send_or_die|,
624 "SendAlerts is using the mocked sendmail routine (claimissues)";
627 $email_object->email->body,
628 "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
629 . $email_object->email->crlf
630 . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
631 "Serial claim letter for 2 issues constructed successfully"
634 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
635 warning_like {
636 $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
637 qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
638 "GetPreparedLetter warns about missing notice template";
639 is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
644 subtest 'GetPreparedLetter' => sub {
645 plan tests => 4;
647 Koha::Notice::Template->new(
649 module => 'test',
650 code => 'test',
651 branchcode => '',
652 message_transport_type => 'email'
654 )->store;
655 my $letter;
656 warning_like {
657 $letter = C4::Letters::GetPreparedLetter(
658 module => 'test',
659 letter_code => 'test',
662 qr{^ERROR: nothing to substitute},
663 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
664 is( $letter, undef,
665 'No letter should be returned by GetPreparedLetter if something went wrong'
668 warning_like {
669 $letter = C4::Letters::GetPreparedLetter(
670 module => 'test',
671 letter_code => 'test',
672 substitute => {}
675 qr{^ERROR: nothing to substitute},
676 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
677 is( $letter, undef,
678 'No letter should be returned by GetPreparedLetter if something went wrong'
685 subtest 'TranslateNotices' => sub {
686 plan tests => 4;
688 t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
690 $dbh->do(
692 INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
693 ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
694 ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
695 | );
696 my $substitute = {};
697 my $letter = C4::Letters::GetPreparedLetter(
698 module => 'test',
699 tables => $tables,
700 letter_code => 'code',
701 message_transport_type => 'email',
702 substitute => $substitute,
705 $letter->{title},
706 'a test',
707 'GetPreparedLetter should return the default one if the lang parameter is not provided'
710 $letter = C4::Letters::GetPreparedLetter(
711 module => 'test',
712 tables => $tables,
713 letter_code => 'code',
714 message_transport_type => 'email',
715 substitute => $substitute,
716 lang => 'es-ES',
718 is( $letter->{title}, 'una prueba',
719 'GetPreparedLetter should return the required notice if it exists' );
721 $letter = C4::Letters::GetPreparedLetter(
722 module => 'test',
723 tables => $tables,
724 letter_code => 'code',
725 message_transport_type => 'email',
726 substitute => $substitute,
727 lang => 'fr-FR',
730 $letter->{title},
731 'a test',
732 'GetPreparedLetter should return the default notice if the one required does not exist'
735 t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
737 $letter = C4::Letters::GetPreparedLetter(
738 module => 'test',
739 tables => $tables,
740 letter_code => 'code',
741 message_transport_type => 'email',
742 substitute => $substitute,
743 lang => 'es-ES',
745 is( $letter->{title}, 'a test',
746 'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
750 subtest 'SendQueuedMessages' => sub {
752 plan tests => 9;
754 t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
755 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
757 my $patron = Koha::Patrons->find($borrowernumber);
758 $dbh->do(q|
759 INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
760 VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
761 |, undef, $borrowernumber
763 eval { C4::Letters::SendQueuedMessages(); };
764 is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
766 my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
767 $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
768 $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
770 warning_like { C4::Letters::SendQueuedMessages(); }
771 qr|Fake send_or_die|,
772 "SendAlerts is using the mocked sendmail routine (claimissues)";
774 my $message = $schema->resultset('MessageQueue')->search({
775 borrowernumber => $borrowernumber,
776 status => 'sent'
777 })->next();
779 is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
781 $message->from_address(),
782 'from@example.com',
783 'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
786 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
788 t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
790 $message_id = C4::Letters::EnqueueLetter($my_message);
791 warning_like { C4::Letters::SendQueuedMessages(); }
792 qr|Fake send_or_die|,
793 "SendAlerts is using the mocked sendmail routine (claimissues)";
795 $message = $schema->resultset('MessageQueue')->search({
796 borrowernumber => $borrowernumber,
797 status => 'sent'
798 })->next();
801 $message->from_address(),
802 'override@example.com',
803 'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
806 $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
807 $my_message->{to_address} = 'fixme@kidclamp.iswrong';
808 $message_id = C4::Letters::EnqueueLetter($my_message);
810 my $number_attempted = C4::Letters::SendQueuedMessages({
811 borrowernumber => -1, # -1 still triggers the borrowernumber condition
812 letter_code => 'PASSWORD_RESET',
814 is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
816 warning_like { C4::Letters::SendQueuedMessages(); }
817 qr|Fake send_or_die|,
818 "SendAlerts is using the mocked sendmail routine (claimissues)";
820 my $sms_message_address = $schema->resultset('MessageQueue')->search({
821 borrowernumber => $borrowernumber,
822 status => 'sent'
823 })->next()->to_address();
824 is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
828 subtest 'get_item_content' => sub {
829 plan tests => 2;
831 t::lib::Mocks::mock_preference('dateformat', 'metric');
832 t::lib::Mocks::mock_preference('timeformat', '24hr');
833 my @items = (
834 {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
835 {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
837 my @item_content_fields = qw( date_due title barcode author itemnumber );
839 my $items_content;
840 for my $item ( @items ) {
841 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
844 my $expected_items_content = <<EOF;
845 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
846 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
848 is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
851 $items_content = q||;
852 for my $item ( @items ) {
853 $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
856 $expected_items_content = <<EOF;
857 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
858 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
860 is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
863 subtest 'Test limit parameter for SendQueuedMessages' => sub {
864 plan tests => 3;
866 my $dbh = C4::Context->dbh;
868 my $borrowernumber = Koha::Patron->new({
869 firstname => 'Jane',
870 surname => 'Smith',
871 categorycode => $patron_category,
872 branchcode => $library->{branchcode},
873 dateofbirth => $date,
874 smsalertnumber => undef,
875 })->store->borrowernumber;
877 $dbh->do(q|DELETE FROM message_queue|);
878 $my_message = {
879 'letter' => {
880 'content' => 'a message',
881 'metadata' => 'metadata',
882 'code' => 'TEST_MESSAGE',
883 'content_type' => 'text/plain',
884 'title' => 'message title'
886 'borrowernumber' => $borrowernumber,
887 'to_address' => undef,
888 'message_transport_type' => 'sms',
889 'from_address' => 'from@example.com'
891 C4::Letters::EnqueueLetter($my_message);
892 C4::Letters::EnqueueLetter($my_message);
893 C4::Letters::EnqueueLetter($my_message);
894 C4::Letters::EnqueueLetter($my_message);
895 C4::Letters::EnqueueLetter($my_message);
896 my $messages_processed = C4::Letters::SendQueuedMessages( { limit => 1 } );
897 is( $messages_processed, 1,
898 'Processed 1 message with limit of 1 and 5 unprocessed messages' );
899 $messages_processed = C4::Letters::SendQueuedMessages( { limit => 2 } );
900 is( $messages_processed, 2,
901 'Processed 2 message with limit of 2 and 4 unprocessed messages' );
902 $messages_processed = C4::Letters::SendQueuedMessages( { limit => 3 } );
903 is( $messages_processed, 2,
904 'Processed 2 message with limit of 3 and 2 unprocessed messages' );