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