Bug 28803: (follow-up) Error details improvement
[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 => 87;
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]->{failure_code}, '', 'Failure code 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 is(
167     $messages->[0]->{failure_code},
168     'MISSING_SMS',
169     'Correct failure code set for borrower with no smsalertnumber set'
170 );
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' );
173
174 # ResendMessage
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' );
183
184 # Delivery notes
185 is( $messages->[0]->{failure_code}, 'MISSING_SMS', 'Failure code set correctly for no smsalertnumber correctly set' );
186
187 # GetLetters
188 my $letters = C4::Letters::GetLetters();
189 is( @$letters, 0, 'GetLetters returns the correct number of letters' );
190
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.
194
195 <<branches.branchname>>
196 <<branches.branchaddress1>>
197 URL: <<OPACBaseURL>>
198
199 The following item(s) is/are currently <<status>>:
200
201 <item> <<count>>. <<items.itemcallnumber>>, Barcode: <<items.barcode>> </item>
202
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>>.
206 };
207
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' );
214
215
216 # getletter
217 subtest 'getletter' => sub {
218     plan tests => 16;
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' );
229
230     t::lib::Mocks::mock_userenv({ branchcode => "anotherlib", flags => 1 });
231
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' );
242 };
243
244
245
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'  );
252
253 # test for overdue_notices.pl
254 my $overdue_rules = {
255     letter1         => 'my code',
256 };
257 my $i = 1;
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' );
261
262 # GetPreparedLetter
263 t::lib::Mocks::mock_preference('OPACBaseURL', 'http://thisisatest.com');
264 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
265
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 );
268
269 my $tables = {
270     borrowers => $borrowernumber,
271     branches => $library->{branchcode},
272     biblio => $biblionumber,
273 };
274 my $substitute = {
275     status => 'overdue',
276 };
277 my $repeat = [
278     {
279         itemcallnumber => 'my callnumber1',
280         barcode        => '1234',
281     },
282     {
283         itemcallnumber => 'my callnumber2',
284         barcode        => '5678',
285     },
286 ];
287 my $prepared_letter = GetPreparedLetter((
288     module      => 'my module',
289     branchcode  => $library->{branchcode},
290     letter_code => 'my code',
291     tables      => $tables,
292     substitute  => $substitute,
293     repeat      => $repeat,
294 ));
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.
300
301 |.$retrieved_library->branchname.qq|
302 |.$retrieved_library->branchaddress1.qq|
303 URL: http://thisisatest.com
304
305 The following item(s) is/are currently $substitute->{status}:
306
307 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
308 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
309
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";
313
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' );
316
317 $prepared_letter = GetPreparedLetter((
318     module                 => 'my module',
319     branchcode             => $library->{branchcode},
320     letter_code            => 'my code',
321     tables                 => $tables,
322     substitute             => $substitute,
323     repeat                 => $repeat,
324     message_transport_type => 'sms',
325 ));
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' );
328
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',
332     branchcode             => '',
333     letter_code            => 'test_date',
334     tables                 => $tables,
335     substitute             => $substitute,
336     repeat                 => $repeat,
337 ));
338 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
339
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',
343     branchcode             => '',
344     letter_code            => 'test_date',
345     tables                 => $tables,
346     substitute             => $substitute,
347     repeat                 => $repeat,
348 ));
349 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
350
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',
354     branchcode             => '',
355     letter_code            => 'test_date',
356     tables                 => $tables,
357     substitute             => $substitute,
358     repeat                 => $repeat,
359 ));
360 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
361
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',
368     branchcode             => '',
369     letter_code            => 'test_date',
370     tables                 => $tables,
371     substitute             => $substitute,
372     repeat                 => $repeat,
373 ));
374 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
375
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>');});
378
379 # Test that _parseletter doesn't modify its parameters bug 15429
380 {
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" );
384 }
385
386 # Correctly format dateexpiry
387 {
388     my $values = { dateexpiry => '2015-12-13', };
389
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' );
394
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' );
399 }
400
401 my $bookseller = Koha::Acquisition::Bookseller->new(
402     {
403         name => "my vendor",
404         address1 => "bookseller's address",
405         phone => "0123456",
406         active => 1,
407         deliverytime => 5,
408     }
409 )->store;
410 my $booksellerid = $bookseller->id;
411
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);
415
416 my $budgetid = C4::Budgets::AddBudget({
417     budget_code => "budget_code_test_letters",
418     budget_name => "budget_name_test_letters",
419 });
420
421 my $bib = MARC::Record->new();
422 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
423     $bib->append_fields(
424         MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
425     );
426 } else {
427     $bib->append_fields(
428         MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
429     );
430 }
431
432 my $logged_in_user = $builder->build_object(
433     {
434         class => 'Koha::Patrons',
435         value => {
436             branchcode => $library->{branchcode},
437             email      => 'some@email.com'
438         }
439     }
440 );
441
442 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
443
444 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
445 my $order = Koha::Acquisition::Order->new(
446     {
447         basketno => $basketno,
448         quantity => 1,
449         biblionumber => $biblionumber,
450         budget_id => $budgetid,
451     }
452 )->store;
453 my $ordernumber = $order->ordernumber;
454
455 Koha::Acquisition::Baskets->find( $basketno )->close;
456 my $err;
457 warning_like {
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");
462
463 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
464 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
465
466 # Ensure that the preference 'ClaimsLog' is set to logging
467 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
468
469 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
470 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
471
472 {
473 warning_like {
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');
480
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');
484 });
485
486 warning_like {
487     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ); }
488     qr{something went wrong},
489     'Warning is printed';
490
491 is($err->{error}, 'something went wrong', "Send exception, error message returned");
492
493 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
494 warning_like {
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.");
499 }
500
501 {
502 warning_like {
503     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
504     qr|Fake send_or_die|,
505     "SendAlerts is using the mocked send_or_die routine";
506
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');
510
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');
514 });
515
516 warning_like {
517     $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
518     qr{something went wrong},
519     'Warning is printed';
520
521 is($err->{error}, 'something went wrong', "Send exception, error message returned");
522 }
523
524 {
525 use C4::Serials;
526
527 my $notes = 'notes';
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
537 );
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];
541
542 my $patron = Koha::Patron->new({
543     firstname    => 'John',
544     surname      => 'Smith',
545     categorycode => $patron_category,
546     branchcode   => $library->{branchcode},
547     dateofbirth  => $date,
548     email        => 'john.smith@test.de',
549 })->store;
550 my $borrowernumber = $patron->borrowernumber;
551 my $subscription = Koha::Subscriptions->find( $subscriptionid );
552 $subscription->add_subscriber( $patron );
553
554 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
555 my $err2;
556 warning_like {
557 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
558     qr|Fake send_or_die|,
559     "SendAlerts is using the mocked send_or_die routine";
560
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');
564
565 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
566
567 my $err3;
568 warning_like {
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");
573
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');
577 });
578
579 warning_like {
580     $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
581     qr{something went wrong},
582     'Warning is printed';
583
584 is($err->{error}, 'something went wrong', "Send exception, error message returned");
585
586 }
587 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
588
589 subtest 'SendAlerts - claimissue' => sub {
590     plan tests => 13;
591
592     use C4::Serials;
593
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>>');});
595
596     my $bookseller = Koha::Acquisition::Bookseller->new(
597         {
598             name => "my vendor",
599             address1 => "bookseller's address",
600             phone => "0123456",
601             active => 1,
602             deliverytime => 5,
603         }
604     )->store;
605     my $booksellerid = $bookseller->id;
606
607     Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
608
609     my $bib = MARC::Record->new();
610     if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
611         $bib->append_fields(
612             MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
613             MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
614         );
615     } else {
616         $bib->append_fields(
617             MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
618             MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
619         );
620     }
621     my ($biblionumber) = AddBiblio($bib, '');
622
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
631     );
632
633     my ($serials_count, @serials) = GetSerials($subscriptionid);
634     my  @serialids = ($serials[0]->{serialid});
635
636     my $err;
637     warning_like {
638         $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
639         qr/^Bookseller .* without emails at/,
640         "Warn on vendor without email address";
641
642     $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
643     $bookseller->contacts->next->email('testemail@mydomain.com')->store;
644
645     # Ensure that the preference 'ClaimsLog' is set to logging
646     t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
647
648     # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
649     t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
650
651     t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
652
653     {
654     warning_like {
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" );
661     is(
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'
665     );
666
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');
670     });
671
672     warning_like {
673         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
674         qr{something went wrong},
675         'Warning is printed';
676
677     is($err->{error}, 'something went wrong', "Send exception, error message returned");
678     }
679
680     {
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});
686
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)";
690
691     is(
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"
697     );
698
699     $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
700     warning_like {
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");
705     }
706
707 };
708
709 subtest 'GetPreparedLetter' => sub {
710     plan tests => 4;
711
712     Koha::Notice::Template->new(
713         {
714             module                 => 'test',
715             code                   => 'test',
716             branchcode             => '',
717             message_transport_type => 'email'
718         }
719     )->store;
720     my $letter;
721     warning_like {
722         $letter = C4::Letters::GetPreparedLetter(
723             module      => 'test',
724             letter_code => 'test',
725         );
726     }
727     qr{^ERROR: nothing to substitute},
728 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
729     is( $letter, undef,
730 'No letter should be returned by GetPreparedLetter if something went wrong'
731     );
732
733     warning_like {
734         $letter = C4::Letters::GetPreparedLetter(
735             module      => 'test',
736             letter_code => 'test',
737             substitute  => {}
738         );
739     }
740     qr{^ERROR: nothing to substitute},
741 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
742     is( $letter, undef,
743 'No letter should be returned by GetPreparedLetter if something went wrong'
744     );
745
746 };
747
748
749
750 subtest 'TranslateNotices' => sub {
751     plan tests => 4;
752
753     t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
754
755     $dbh->do(
756         q|
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');
760     | );
761     my $substitute = {};
762     my $letter = C4::Letters::GetPreparedLetter(
763             module                 => 'test',
764             tables                 => $tables,
765             letter_code            => 'code',
766             message_transport_type => 'email',
767             substitute             => $substitute,
768     );
769     is(
770         $letter->{title},
771         'a test',
772         'GetPreparedLetter should return the default one if the lang parameter is not provided'
773     );
774
775     $letter = C4::Letters::GetPreparedLetter(
776             module                 => 'test',
777             tables                 => $tables,
778             letter_code            => 'code',
779             message_transport_type => 'email',
780             substitute             => $substitute,
781             lang                   => 'es-ES',
782     );
783     is( $letter->{title}, 'una prueba',
784         'GetPreparedLetter should return the required notice if it exists' );
785
786     $letter = C4::Letters::GetPreparedLetter(
787             module                 => 'test',
788             tables                 => $tables,
789             letter_code            => 'code',
790             message_transport_type => 'email',
791             substitute             => $substitute,
792             lang                   => 'fr-FR',
793     );
794     is(
795         $letter->{title},
796         'a test',
797         'GetPreparedLetter should return the default notice if the one required does not exist'
798     );
799
800     t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
801
802     $letter = C4::Letters::GetPreparedLetter(
803             module                 => 'test',
804             tables                 => $tables,
805             letter_code            => 'code',
806             message_transport_type => 'email',
807             substitute             => $substitute,
808             lang                   => 'es-ES',
809     );
810     is( $letter->{title}, 'a test',
811         'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
812
813 };
814
815 subtest 'SendQueuedMessages' => sub {
816
817     plan tests => 12;
818
819     t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
820     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
821
822     my $patron = Koha::Patrons->find($borrowernumber);
823     $dbh->do(q|
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
827     );
828     eval { C4::Letters::SendQueuedMessages(); };
829     is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
830
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
834
835     warning_like { C4::Letters::SendQueuedMessages(); }
836         qr|Fake send_or_die|,
837         "SendAlerts is using the mocked send_or_die routine (claimissues)";
838
839     my $message = $schema->resultset('MessageQueue')->search({
840         borrowernumber => $borrowernumber,
841         status => 'sent'
842     })->next();
843
844     is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
845     is(
846         $message->from_address(),
847         'from@example.com',
848         'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
849     );
850
851     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
852
853     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
854
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)";
859
860     $message = $schema->resultset('MessageQueue')->search({
861         borrowernumber => $borrowernumber,
862         status => 'sent'
863     })->next();
864
865     is(
866         $message->from_address(),
867         'override@example.com',
868         'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
869     );
870
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);
874
875     my $number_attempted = C4::Letters::SendQueuedMessages({
876         borrowernumber => -1, # -1 still triggers the borrowernumber condition
877         letter_code    => 'PASSWORD_RESET',
878     });
879     is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
880
881     warning_like { C4::Letters::SendQueuedMessages(); }
882         qr|Fake send_or_die|,
883         "SendAlerts is using the mocked send_or_die routine (claimissues)";
884
885     my $sms_message_address = $schema->resultset('MessageQueue')->search({
886         borrowernumber => $borrowernumber,
887         status => 'sent'
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' );
890
891 };
892
893 subtest 'get_item_content' => sub {
894     plan tests => 2;
895
896     t::lib::Mocks::mock_preference('dateformat', 'metric');
897     t::lib::Mocks::mock_preference('timeformat', '24hr');
898     my @items = (
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 },
901     );
902     my @item_content_fields = qw( date_due title barcode author itemnumber );
903
904     my $items_content;
905     for my $item ( @items ) {
906         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
907     }
908
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
912 EOF
913     is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
914
915
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, } );
919     }
920
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
924 EOF
925     is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
926 };
927
928 subtest 'Test limit parameter for SendQueuedMessages' => sub {
929     plan tests => 3;
930
931     my $dbh = C4::Context->dbh;
932
933     my $borrowernumber = Koha::Patron->new({
934         firstname    => 'Jane',
935         surname      => 'Smith',
936         categorycode => $patron_category,
937         branchcode   => $library->{branchcode},
938         dateofbirth  => $date,
939         smsalertnumber => undef,
940     })->store->borrowernumber;
941
942     $dbh->do(q|DELETE FROM message_queue|);
943     $my_message = {
944         'letter' => {
945             'content'      => 'a message',
946             'metadata'     => 'metadata',
947             'code'         => 'TEST_MESSAGE',
948             'content_type' => 'text/plain',
949             'title'        => 'message title'
950         },
951         'borrowernumber'         => $borrowernumber,
952         'to_address'             => undef,
953         'message_transport_type' => 'sms',
954         'from_address'           => 'from@example.com'
955     };
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' );
970 };
971
972 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
973
974     plan tests => 7;
975
976     my $dbh = C4::Context->dbh;
977
978     my $borrowernumber = Koha::Patron->new({
979         firstname    => 'Jane',
980         surname      => 'Smith',
981         categorycode => $patron_category,
982         branchcode   => $library->{branchcode},
983         dateofbirth  => $date,
984         smsalertnumber => undef,
985     })->store->borrowernumber;
986
987     $dbh->do(q|DELETE FROM message_queue|);
988     $my_message = {
989         'letter' => {
990             'content'      => 'a message',
991             'metadata'     => 'metadata',
992             'code'         => 'TEST_MESSAGE',
993             'content_type' => 'text/plain',
994             'title'        => 'message title'
995         },
996         'borrowernumber'         => $borrowernumber,
997         'to_address'             => 'to@example.org',
998         'message_transport_type' => 'email',
999         'from_address'           => '@example.com' # invalid from_address
1000     };
1001     my $message_id = C4::Letters::EnqueueLetter($my_message);
1002     my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1003     is( $processed, 1, 'Processed 1 message when one message_id passed' );
1004     my $message_1 = C4::Letters::GetMessage($message_id);
1005     is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1006     is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1007
1008     $my_message->{from_address} = 'root@example.org'; # valid from_address
1009     $message_id = C4::Letters::EnqueueLetter($my_message);
1010     warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1011         qr|Fake send_or_die|,
1012         "SendQueuedMessages is using the mocked send_or_die routine";
1013     $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1014     my $message_2 = C4::Letters::GetMessage($message_id);
1015     is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1016     is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
1017 };