Bug 14723: Additional delivery notes to messages
[koha.git] / t / db_dependent / Letters.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Copyright (C) 2013 Equinox Software, Inc.
6 #
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.
11 #
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.
16 #
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>.
19
20 use Modern::Perl;
21 use Test::More tests => 86;
22 use Test::MockModule;
23 use Test::Warn;
24 use Test::Exception;
25
26 use Email::Sender::Failure;
27
28 use MARC::Record;
29
30 use utf8;
31
32 my ( $email_object, $sendmail_params );
33
34 my $email_sender_module = Test::MockModule->new('Email::Stuffer');
35 $email_sender_module->mock(
36     'send_or_die',
37     sub {
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";
42     }
43 );
44
45 use_ok('C4::Context');
46 use_ok('C4::Members');
47 use_ok('C4::Acquisition');
48 use_ok('C4::Biblio');
49 use_ok('C4::Letters');
50 use t::lib::Mocks;
51 use t::lib::TestBuilder;
52 use Koha::Database;
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;
57 use Koha::Libraries;
58 use Koha::Notice::Templates;
59 use Koha::Patrons;
60 use Koha::Subscriptions;
61 my $schema = Koha::Database->schema;
62 $schema->storage->txn_begin();
63
64 my $builder = t::lib::TestBuilder->new;
65 my $dbh = C4::Context->dbh;
66
67 $dbh->do(q|DELETE FROM letter|);
68 $dbh->do(q|DELETE FROM message_queue|);
69 $dbh->do(q|DELETE FROM message_transport_types|);
70
71 my $library = $builder->build({
72     source => 'Branch',
73     value  => {
74         branchemail      => 'branchemail@address.com',
75         branchreplyto    => 'branchreplyto@address.com',
76         branchreturnpath => 'branchreturnpath@address.com',
77     }
78 });
79 my $patron_category = $builder->build({ source => 'Category' })->{categorycode};
80 my $date = dt_from_string;
81 my $borrowernumber = Koha::Patron->new({
82     firstname    => 'Jane',
83     surname      => 'Smith',
84     categorycode => $patron_category,
85     branchcode   => $library->{branchcode},
86     dateofbirth  => $date,
87     smsalertnumber => undef,
88 })->store->borrowernumber;
89
90 my $marc_record = MARC::Record->new;
91 my( $biblionumber, $biblioitemnumber ) = AddBiblio( $marc_record, '' );
92
93
94
95 # GetMessageTransportTypes
96 my $mtts = C4::Letters::GetMessageTransportTypes();
97 is( @$mtts, 0, 'GetMessageTransportTypes returns the correct number of message types' );
98
99 $dbh->do(q|
100     INSERT INTO message_transport_types( message_transport_type ) VALUES ('email'), ('phone'), ('print'), ('sms')
101 |);
102 $mtts = C4::Letters::GetMessageTransportTypes();
103 is_deeply( $mtts, ['email', 'phone', 'print', 'sms'], 'GetMessageTransportTypes returns all values' );
104
105
106 # EnqueueLetter
107 is( C4::Letters::EnqueueLetter(), undef, 'EnqueueLetter without argument returns undef' );
108
109 my $my_message = {
110     borrowernumber         => $borrowernumber,
111     message_transport_type => 'sms',
112     to_address             => undef,
113     from_address           => 'from@example.com',
114 };
115 my $message_id = C4::Letters::EnqueueLetter($my_message);
116 is( $message_id, undef, 'EnqueueLetter without the letter argument returns undef' );
117
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',
125 };
126
127 $message_id = C4::Letters::EnqueueLetter($my_message);
128 is( $message_id, undef, 'EnqueueLetter without the message type argument argument returns undef' );
129
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');
133
134
135 # GetQueuedMessages
136 my $messages = C4::Letters::GetQueuedMessages();
137 is( @$messages, 1, 'GetQueuedMessages without argument returns all the entries' );
138
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]->{delivery_note}, '', 'Delivery note for successful message correctly empty');
150
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;
154
155 # SendQueuedMessages
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 });
161 is(
162     $messages->[0]->{status},
163     'failed',
164     'message marked failed if tried to send SMS message for borrower with no smsalertnumber set (bug 11208)'
165 );
166 isnt($messages->[0]->{updated_on}, $messages->[0]->{time_queued}, 'Time status changed differs from time queued when status changes' );
167 is(dt_from_string($messages->[0]->{time_queued}), $yesterday, 'Time queued remaines inmutable' );
168
169 # ResendMessage
170 my $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
171 my $message = C4::Letters::GetMessage( $messages->[0]->{message_id});
172 is( $resent, 1, 'The message should have been resent' );
173 is($message->{status},'pending', 'ResendMessage sets status to pending correctly (bug 12426)');
174 $resent = C4::Letters::ResendMessage($messages->[0]->{message_id});
175 is( $resent, 0, 'The message should not have been resent again' );
176 $resent = C4::Letters::ResendMessage();
177 is( $resent, undef, 'ResendMessage should return undef if not message_id given' );
178
179 # Delivery notes
180 is($messages->[0]->{delivery_note}, 'Missing SMS number',
181    'Delivery note for no smsalertnumber correctly set');
182
183
184 # GetLetters
185 my $letters = C4::Letters::GetLetters();
186 is( @$letters, 0, 'GetLetters returns the correct number of letters' );
187
188 my $title = q|<<branches.branchname>> - <<status>>|;
189 my $content = q{Dear <<borrowers.firstname>> <<borrowers.surname>>,
190 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.
191
192 <<branches.branchname>>
193 <<branches.branchaddress1>>
194 URL: <<OPACBaseURL>>
195
196 The following item(s) is/are currently <<status>>:
197
198 <item> <<count>>. <<items.itemcallnumber>>, Barcode: <<items.barcode>> </item>
199
200 Thank-you for your prompt attention to this matter.
201 Don't forget your date of birth: <<borrowers.dateofbirth>>.
202 Look at this wonderful biblio timestamp: <<biblio.timestamp>>.
203 };
204
205 $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 );
206 $letters = C4::Letters::GetLetters();
207 is( @$letters, 1, 'GetLetters returns the correct number of letters' );
208 is( $letters->[0]->{module}, 'my module', 'GetLetters gets the module correctly' );
209 is( $letters->[0]->{code}, 'my code', 'GetLetters gets the code correctly' );
210 is( $letters->[0]->{name}, 'my name', 'GetLetters gets the name correctly' );
211
212
213 # getletter
214 subtest 'getletter' => sub {
215     plan tests => 16;
216     t::lib::Mocks::mock_preference('IndependentBranches', 0);
217     my $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' );
226
227     t::lib::Mocks::mock_userenv({ branchcode => "anotherlib", flags => 1 });
228
229     t::lib::Mocks::mock_preference('IndependentBranches', 1);
230     $letter = C4::Letters::getletter('my module', 'my code', $library->{branchcode}, 'email');
231     is( $letter->{branchcode}, $library->{branchcode}, 'GetLetters gets the branch code correctly' );
232     is( $letter->{module}, 'my module', 'GetLetters gets the module correctly' );
233     is( $letter->{code}, 'my code', 'GetLetters gets the code correctly' );
234     is( $letter->{name}, 'my name', 'GetLetters gets the name correctly' );
235     is( $letter->{is_html}, 1, 'GetLetters gets the boolean is_html correctly' );
236     is( $letter->{title}, $title, 'GetLetters gets the title correctly' );
237     is( $letter->{content}, $content, 'GetLetters gets the content correctly' );
238     is( $letter->{message_transport_type}, 'email', 'GetLetters gets the message type correctly' );
239 };
240
241
242
243 # Regression test for Bug 14206
244 $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 );
245 my $letter14206_a = C4::Letters::getletter('my module', 'my code', 'FFL' );
246 is( $letter14206_a->{message_transport_type}, 'print', 'Bug 14206 - message_transport_type not passed, correct mtt detected' );
247 my $letter14206_b = C4::Letters::getletter('my module', 'my code', 'FFL', 'print');
248 is( $letter14206_b->{message_transport_type}, 'print', 'Bug 14206 - message_transport_type passed, correct mtt detected'  );
249
250 # test for overdue_notices.pl
251 my $overdue_rules = {
252     letter1         => 'my code',
253 };
254 my $i = 1;
255 my $branchcode = 'FFL';
256 my $letter14206_c = C4::Letters::getletter('my module', $overdue_rules->{"letter$i"}, $branchcode);
257 is( $letter14206_c->{message_transport_type}, 'print', 'Bug 14206 - correct mtt detected for call from overdue_notices.pl' );
258
259 # GetPreparedLetter
260 t::lib::Mocks::mock_preference('OPACBaseURL', 'http://thisisatest.com');
261 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
262
263 my $sms_content = 'This is a SMS for an <<status>>';
264 $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 );
265
266 my $tables = {
267     borrowers => $borrowernumber,
268     branches => $library->{branchcode},
269     biblio => $biblionumber,
270 };
271 my $substitute = {
272     status => 'overdue',
273 };
274 my $repeat = [
275     {
276         itemcallnumber => 'my callnumber1',
277         barcode        => '1234',
278     },
279     {
280         itemcallnumber => 'my callnumber2',
281         barcode        => '5678',
282     },
283 ];
284 my $prepared_letter = GetPreparedLetter((
285     module      => 'my module',
286     branchcode  => $library->{branchcode},
287     letter_code => 'my code',
288     tables      => $tables,
289     substitute  => $substitute,
290     repeat      => $repeat,
291 ));
292 my $retrieved_library = Koha::Libraries->find($library->{branchcode});
293 my $my_title_letter = $retrieved_library->branchname . qq| - $substitute->{status}|;
294 my $biblio_timestamp = dt_from_string( GetBiblioData($biblionumber)->{timestamp} );
295 my $my_content_letter = qq|Dear Jane Smith,
296 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.
297
298 |.$retrieved_library->branchname.qq|
299 |.$retrieved_library->branchaddress1.qq|
300 URL: http://thisisatest.com
301
302 The following item(s) is/are currently $substitute->{status}:
303
304 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
305 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
306
307 Thank-you for your prompt attention to this matter.
308 Don't forget your date of birth: | . output_pref({ dt => $date, dateonly => 1 }) . q|.
309 Look at this wonderful biblio timestamp: | . output_pref({ dt => $biblio_timestamp })  . ".\n";
310
311 is( $prepared_letter->{title}, $my_title_letter, 'GetPreparedLetter returns the title correctly' );
312 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
313
314 $prepared_letter = GetPreparedLetter((
315     module                 => 'my module',
316     branchcode             => $library->{branchcode},
317     letter_code            => 'my code',
318     tables                 => $tables,
319     substitute             => $substitute,
320     repeat                 => $repeat,
321     message_transport_type => 'sms',
322 ));
323 $my_content_letter = qq|This is a SMS for an $substitute->{status}|;
324 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
325
326 $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>>.');});
327 $prepared_letter = GetPreparedLetter((
328     module                 => 'test_date',
329     branchcode             => '',
330     letter_code            => 'test_date',
331     tables                 => $tables,
332     substitute             => $substitute,
333     repeat                 => $repeat,
334 ));
335 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
336
337 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp | dateonly>>.' WHERE code = 'test_date';});
338 $prepared_letter = GetPreparedLetter((
339     module                 => 'test_date',
340     branchcode             => '',
341     letter_code            => 'test_date',
342     tables                 => $tables,
343     substitute             => $substitute,
344     repeat                 => $repeat,
345 ));
346 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
347
348 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp|dateonly >>.' WHERE code = 'test_date';});
349 $prepared_letter = GetPreparedLetter((
350     module                 => 'test_date',
351     branchcode             => '',
352     letter_code            => 'test_date',
353     tables                 => $tables,
354     substitute             => $substitute,
355     repeat                 => $repeat,
356 ));
357 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
358
359 t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
360 my $yesterday_night = $date->clone->add( days => -1 )->set_hour(22);
361 $dbh->do(q|UPDATE biblio SET timestamp = ? WHERE biblionumber = ?|, undef, $yesterday_night, $biblionumber );
362 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp>>.' WHERE code = 'test_date';});
363 $prepared_letter = GetPreparedLetter((
364     module                 => 'test_date',
365     branchcode             => '',
366     letter_code            => 'test_date',
367     tables                 => $tables,
368     substitute             => $substitute,
369     repeat                 => $repeat,
370 ));
371 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
372
373 $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>');});
374 $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>');});
375
376 # Test that _parseletter doesn't modify its parameters bug 15429
377 {
378     my $values = { dateexpiry => '2015-12-13', };
379     C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
380     is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
381 }
382
383 # Correctly format dateexpiry
384 {
385     my $values = { dateexpiry => '2015-12-13', };
386
387     t::lib::Mocks::mock_preference('dateformat', 'metric');
388     t::lib::Mocks::mock_preference('timeformat', '24hr');
389     my $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
390     is( $letter->{content}, 'expiry on 13/12/2015' );
391
392     t::lib::Mocks::mock_preference('dateformat', 'metric');
393     t::lib::Mocks::mock_preference('timeformat', '12hr');
394     $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
395     is( $letter->{content}, 'expiry on 13/12/2015' );
396 }
397
398 my $bookseller = Koha::Acquisition::Bookseller->new(
399     {
400         name => "my vendor",
401         address1 => "bookseller's address",
402         phone => "0123456",
403         active => 1,
404         deliverytime => 5,
405     }
406 )->store;
407 my $booksellerid = $bookseller->id;
408
409 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith',  phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
410 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues      => 1, booksellerid => $booksellerid } )->store;
411 my $basketno = NewBasket($booksellerid, 1);
412
413 my $budgetid = C4::Budgets::AddBudget({
414     budget_code => "budget_code_test_letters",
415     budget_name => "budget_name_test_letters",
416 });
417
418 my $bib = MARC::Record->new();
419 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
420     $bib->append_fields(
421         MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
422     );
423 } else {
424     $bib->append_fields(
425         MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
426     );
427 }
428
429 my $logged_in_user = $builder->build_object(
430     {
431         class => 'Koha::Patrons',
432         value => {
433             branchcode => $library->{branchcode},
434             email      => 'some@email.com'
435         }
436     }
437 );
438
439 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
440
441 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
442 my $order = Koha::Acquisition::Order->new(
443     {
444         basketno => $basketno,
445         quantity => 1,
446         biblionumber => $biblionumber,
447         budget_id => $budgetid,
448     }
449 )->store;
450 my $ordernumber = $order->ordernumber;
451
452 Koha::Acquisition::Baskets->find( $basketno )->close;
453 my $err;
454 warning_like {
455     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
456     qr/^Bookseller .* without emails at/,
457     "SendAlerts prints a warning";
458 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
459
460 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
461 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
462
463 # Ensure that the preference 'LetterLog' is set to logging
464 t::lib::Mocks::mock_preference( 'LetterLog', 'on' );
465
466 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
467 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
468
469 {
470 warning_like {
471     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
472     qr|Fake send_or_die|,
473     "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
474 is($err, 1, "Successfully sent order.");
475 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent order");
476 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Order notice text constructed successfully');
477
478 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
479 $mocked_koha_email->mock( 'send_or_die', sub {
480     Email::Sender::Failure->throw('something went wrong');
481 });
482
483 warning_like {
484     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ); }
485     qr{something went wrong},
486     'Warning is printed';
487
488 is($err->{error}, 'something went wrong', "Send exception, error message returned");
489
490 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
491 warning_like {
492     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
493     qr/No orderacquisition TESTACQORDER letter transported by email/,
494     "GetPreparedLetter warns about missing notice template";
495 is($err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined.");
496 }
497
498 {
499 warning_like {
500     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
501     qr|Fake send_or_die|,
502     "SendAlerts is using the mocked send_or_die routine";
503
504 is($err, 1, "Successfully sent claim");
505 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
506 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
507
508 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
509 $mocked_koha_email->mock( 'send_or_die', sub {
510     Email::Sender::Failure->throw('something went wrong');
511 });
512
513 warning_like {
514     $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
515     qr{something went wrong},
516     'Warning is printed';
517
518 is($err->{error}, 'something went wrong', "Send exception, error message returned");
519 }
520
521 {
522 use C4::Serials;
523
524 my $notes = 'notes';
525 my $internalnotes = 'intnotes';
526 $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
527 my $subscriptionid = NewSubscription(
528      undef,      "",     undef, undef, undef, $biblionumber,
529     '2013-01-01', 1, undef, undef,  undef,
530     undef,      undef,  undef, undef, undef, undef,
531     1,          $notes,undef, '2013-01-01', undef, 1,
532     undef,       undef,  0,    $internalnotes,  0,
533     undef, undef, 0,          undef,         '2013-12-31', 0
534 );
535 $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>>');});
536 my ($serials_count, @serials) = GetSerials($subscriptionid);
537 my $serial = $serials[0];
538
539 my $patron = Koha::Patron->new({
540     firstname    => 'John',
541     surname      => 'Smith',
542     categorycode => $patron_category,
543     branchcode   => $library->{branchcode},
544     dateofbirth  => $date,
545     email        => 'john.smith@test.de',
546 })->store;
547 my $borrowernumber = $patron->borrowernumber;
548 my $subscription = Koha::Subscriptions->find( $subscriptionid );
549 $subscription->add_subscriber( $patron );
550
551 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
552 my $err2;
553 warning_like {
554 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
555     qr|Fake send_or_die|,
556     "SendAlerts is using the mocked send_or_die routine";
557
558 is($err2, 1, "Successfully sent serial notification");
559 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
560 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
561
562 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
563
564 my $err3;
565 warning_like {
566 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
567     qr|Fake send_or_die|,
568     "SendAlerts is using the mocked send_or_die routine";
569 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
570
571 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
572 $mocked_koha_email->mock( 'send_or_die', sub {
573     Email::Sender::Failure->throw('something went wrong');
574 });
575
576 warning_like {
577     $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
578     qr{something went wrong},
579     'Warning is printed';
580
581 is($err->{error}, 'something went wrong', "Send exception, error message returned");
582
583 }
584 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
585
586 subtest 'SendAlerts - claimissue' => sub {
587     plan tests => 13;
588
589     use C4::Serials;
590
591     $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>>');});
592
593     my $bookseller = Koha::Acquisition::Bookseller->new(
594         {
595             name => "my vendor",
596             address1 => "bookseller's address",
597             phone => "0123456",
598             active => 1,
599             deliverytime => 5,
600         }
601     )->store;
602     my $booksellerid = $bookseller->id;
603
604     Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
605
606     my $bib = MARC::Record->new();
607     if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
608         $bib->append_fields(
609             MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
610             MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
611         );
612     } else {
613         $bib->append_fields(
614             MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
615             MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
616         );
617     }
618     my ($biblionumber) = AddBiblio($bib, '');
619
620     $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
621     my $subscriptionid = NewSubscription(
622          undef, "", $booksellerid, undef, undef, $biblionumber,
623         '2013-01-01', 1, undef, undef,  undef,
624         undef,  undef,  undef, undef, undef, undef,
625         1, 'public',undef, '2013-01-01', undef, 1,
626         undef, undef,  0, 'internal',  0,
627         undef, undef, 0,  undef, '2013-12-31', 0
628     );
629
630     my ($serials_count, @serials) = GetSerials($subscriptionid);
631     my  @serialids = ($serials[0]->{serialid});
632
633     my $err;
634     warning_like {
635         $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
636         qr/^Bookseller .* without emails at/,
637         "Warn on vendor without email address";
638
639     $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
640     $bookseller->contacts->next->email('testemail@mydomain.com')->store;
641
642     # Ensure that the preference 'LetterLog' is set to logging
643     t::lib::Mocks::mock_preference( 'LetterLog', 'on' );
644
645     # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
646     t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
647
648     t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
649
650     {
651     warning_like {
652         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
653         qr|Fake send_or_die|,
654         "SendAlerts is using the mocked send_or_die routine (claimissues)";
655     is( $err, 1, "Successfully sent claim" );
656     is( $email_object->email->header('To'),
657         'testemail@mydomain.com', "mailto correct in sent claim" );
658     is(
659         $email_object->email->body,
660         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
661         'Serial claim letter for 1 issue constructed successfully'
662     );
663
664     my $mocked_koha_email = Test::MockModule->new('Koha::Email');
665     $mocked_koha_email->mock( 'send_or_die', sub {
666             Email::Sender::Failure->throw('something went wrong');
667     });
668
669     warning_like {
670         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
671         qr{something went wrong},
672         'Warning is printed';
673
674     is($err->{error}, 'something went wrong', "Send exception, error message returned");
675     }
676
677     {
678     my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
679     my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
680     ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
681     ($serials_count, @serials) = GetSerials($subscriptionid);
682     push @serialids, ($serials[1]->{serialid});
683
684     warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
685         qr|Fake send_or_die|,
686         "SendAlerts is using the mocked send_or_die routine (claimissues)";
687
688     is(
689         $email_object->email->body,
690         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
691           . $email_object->email->crlf
692           . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
693         "Serial claim letter for 2 issues constructed successfully"
694     );
695
696     $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
697     warning_like {
698         $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
699         qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
700         "GetPreparedLetter warns about missing notice template";
701     is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
702     }
703
704 };
705
706 subtest 'GetPreparedLetter' => sub {
707     plan tests => 4;
708
709     Koha::Notice::Template->new(
710         {
711             module                 => 'test',
712             code                   => 'test',
713             branchcode             => '',
714             message_transport_type => 'email'
715         }
716     )->store;
717     my $letter;
718     warning_like {
719         $letter = C4::Letters::GetPreparedLetter(
720             module      => 'test',
721             letter_code => 'test',
722         );
723     }
724     qr{^ERROR: nothing to substitute},
725 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
726     is( $letter, undef,
727 'No letter should be returned by GetPreparedLetter if something went wrong'
728     );
729
730     warning_like {
731         $letter = C4::Letters::GetPreparedLetter(
732             module      => 'test',
733             letter_code => 'test',
734             substitute  => {}
735         );
736     }
737     qr{^ERROR: nothing to substitute},
738 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
739     is( $letter, undef,
740 'No letter should be returned by GetPreparedLetter if something went wrong'
741     );
742
743 };
744
745
746
747 subtest 'TranslateNotices' => sub {
748     plan tests => 4;
749
750     t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
751
752     $dbh->do(
753         q|
754         INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
755         ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
756         ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
757     | );
758     my $substitute = {};
759     my $letter = C4::Letters::GetPreparedLetter(
760             module                 => 'test',
761             tables                 => $tables,
762             letter_code            => 'code',
763             message_transport_type => 'email',
764             substitute             => $substitute,
765     );
766     is(
767         $letter->{title},
768         'a test',
769         'GetPreparedLetter should return the default one if the lang parameter is not provided'
770     );
771
772     $letter = C4::Letters::GetPreparedLetter(
773             module                 => 'test',
774             tables                 => $tables,
775             letter_code            => 'code',
776             message_transport_type => 'email',
777             substitute             => $substitute,
778             lang                   => 'es-ES',
779     );
780     is( $letter->{title}, 'una prueba',
781         'GetPreparedLetter should return the required notice if it exists' );
782
783     $letter = C4::Letters::GetPreparedLetter(
784             module                 => 'test',
785             tables                 => $tables,
786             letter_code            => 'code',
787             message_transport_type => 'email',
788             substitute             => $substitute,
789             lang                   => 'fr-FR',
790     );
791     is(
792         $letter->{title},
793         'a test',
794         'GetPreparedLetter should return the default notice if the one required does not exist'
795     );
796
797     t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
798
799     $letter = C4::Letters::GetPreparedLetter(
800             module                 => 'test',
801             tables                 => $tables,
802             letter_code            => 'code',
803             message_transport_type => 'email',
804             substitute             => $substitute,
805             lang                   => 'es-ES',
806     );
807     is( $letter->{title}, 'a test',
808         'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
809
810 };
811
812 subtest 'SendQueuedMessages' => sub {
813
814     plan tests => 12;
815
816     t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
817     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
818
819     my $patron = Koha::Patrons->find($borrowernumber);
820     $dbh->do(q|
821         INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
822         VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
823         |, undef, $borrowernumber
824     );
825     eval { C4::Letters::SendQueuedMessages(); };
826     is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
827
828     my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
829     $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
830     $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
831
832     warning_like { C4::Letters::SendQueuedMessages(); }
833         qr|Fake send_or_die|,
834         "SendAlerts is using the mocked send_or_die routine (claimissues)";
835
836     my $message = $schema->resultset('MessageQueue')->search({
837         borrowernumber => $borrowernumber,
838         status => 'sent'
839     })->next();
840
841     is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
842     is(
843         $message->from_address(),
844         'from@example.com',
845         'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
846     );
847
848     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
849
850     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
851
852     $message_id = C4::Letters::EnqueueLetter($my_message);
853     warning_like { C4::Letters::SendQueuedMessages(); }
854         qr|Fake send_or_die|,
855         "SendAlerts is using the mocked send_or_die routine (claimissues)";
856
857     $message = $schema->resultset('MessageQueue')->search({
858         borrowernumber => $borrowernumber,
859         status => 'sent'
860     })->next();
861
862     is(
863         $message->from_address(),
864         'override@example.com',
865         'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
866     );
867
868     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
869     $my_message->{to_address} = 'fixme@kidclamp.iswrong';
870     $message_id = C4::Letters::EnqueueLetter($my_message);
871
872     my $number_attempted = C4::Letters::SendQueuedMessages({
873         borrowernumber => -1, # -1 still triggers the borrowernumber condition
874         letter_code    => 'PASSWORD_RESET',
875     });
876     is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
877
878     warning_like { C4::Letters::SendQueuedMessages(); }
879         qr|Fake send_or_die|,
880         "SendAlerts is using the mocked send_or_die routine (claimissues)";
881
882     my $sms_message_address = $schema->resultset('MessageQueue')->search({
883         borrowernumber => $borrowernumber,
884         status => 'sent'
885     })->next()->to_address();
886     is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
887
888 };
889
890 subtest 'get_item_content' => sub {
891     plan tests => 2;
892
893     t::lib::Mocks::mock_preference('dateformat', 'metric');
894     t::lib::Mocks::mock_preference('timeformat', '24hr');
895     my @items = (
896         {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
897         {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
898     );
899     my @item_content_fields = qw( date_due title barcode author itemnumber );
900
901     my $items_content;
902     for my $item ( @items ) {
903         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
904     }
905
906     my $expected_items_content = <<EOF;
907 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
908 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
909 EOF
910     is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
911
912
913     $items_content = q||;
914     for my $item ( @items ) {
915         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
916     }
917
918     $expected_items_content = <<EOF;
919 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
920 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
921 EOF
922     is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
923 };
924
925 subtest 'Test limit parameter for SendQueuedMessages' => sub {
926     plan tests => 3;
927
928     my $dbh = C4::Context->dbh;
929
930     my $borrowernumber = Koha::Patron->new({
931         firstname    => 'Jane',
932         surname      => 'Smith',
933         categorycode => $patron_category,
934         branchcode   => $library->{branchcode},
935         dateofbirth  => $date,
936         smsalertnumber => undef,
937     })->store->borrowernumber;
938
939     $dbh->do(q|DELETE FROM message_queue|);
940     $my_message = {
941         'letter' => {
942             'content'      => 'a message',
943             'metadata'     => 'metadata',
944             'code'         => 'TEST_MESSAGE',
945             'content_type' => 'text/plain',
946             'title'        => 'message title'
947         },
948         'borrowernumber'         => $borrowernumber,
949         'to_address'             => undef,
950         'message_transport_type' => 'sms',
951         'from_address'           => 'from@example.com'
952     };
953     C4::Letters::EnqueueLetter($my_message);
954     C4::Letters::EnqueueLetter($my_message);
955     C4::Letters::EnqueueLetter($my_message);
956     C4::Letters::EnqueueLetter($my_message);
957     C4::Letters::EnqueueLetter($my_message);
958     my $messages_processed = C4::Letters::SendQueuedMessages( { limit => 1 } );
959     is( $messages_processed, 1,
960         'Processed 1 message with limit of 1 and 5 unprocessed messages' );
961     $messages_processed = C4::Letters::SendQueuedMessages( { limit => 2 } );
962     is( $messages_processed, 2,
963         'Processed 2 message with limit of 2 and 4 unprocessed messages' );
964     $messages_processed = C4::Letters::SendQueuedMessages( { limit => 3 } );
965     is( $messages_processed, 2,
966         'Processed 2 message with limit of 3 and 2 unprocessed messages' );
967 };
968
969 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
970
971     plan tests => 6;
972
973     my $dbh = C4::Context->dbh;
974
975     my $borrowernumber = Koha::Patron->new({
976         firstname    => 'Jane',
977         surname      => 'Smith',
978         categorycode => $patron_category,
979         branchcode   => $library->{branchcode},
980         dateofbirth  => $date,
981         smsalertnumber => undef,
982     })->store->borrowernumber;
983
984     $dbh->do(q|DELETE FROM message_queue|);
985     $my_message = {
986         'letter' => {
987             'content'      => 'a message',
988             'metadata'     => 'metadata',
989             'code'         => 'TEST_MESSAGE',
990             'content_type' => 'text/plain',
991             'title'        => 'message title'
992         },
993         'borrowernumber'         => $borrowernumber,
994         'to_address'             => 'to@example.org',
995         'message_transport_type' => 'email',
996         'from_address'           => 'root@localhost.' # invalid KohaAdminEmailAddress
997     };
998     my $message_id = C4::Letters::EnqueueLetter($my_message);
999     throws_ok {
1000         C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1001     } 'Koha::Exceptions::BadParameter',
1002     'Exception thrown if invalid email is passed';
1003     my $message_1 = C4::Letters::GetMessage($message_id);
1004     # FIXME must be 'failed'
1005     is( $message_1->{status}, 'pending', 'Invalid KohaAdminEmailAddress => status pending' );
1006
1007     $my_message->{from_address} = 'root@example.org'; # valid KohaAdminEmailAddress
1008     $message_id = C4::Letters::EnqueueLetter($my_message);
1009     warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1010         qr|Fake send_or_die|,
1011         "SendQueuedMessages is using the mocked send_or_die routine";
1012     $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1013     my $message_2 = C4::Letters::GetMessage($message_id);
1014     is( $message_1->{status}, 'pending', 'Message 1 status is unchanged' ); # Must be 'failed'
1015     is( $message_2->{status}, 'sent', 'Valid KohaAdminEmailAddress => status sent' );
1016 };