Bug 33815: Regression tests
[koha.git] / t / db_dependent / Letters.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Copyright (C) 2013 Equinox Software, Inc.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21 use Test::More tests => 86;
22 use Test::MockModule;
23 use Test::Warn;
24 use Test::Exception;
25
26 use Email::Sender::Failure;
27
28 use MARC::Record;
29
30 use utf8;
31
32 my ( $email_object, $sendmail_params );
33
34 my $email_sender_module = Test::MockModule->new('Email::Stuffer');
35 $email_sender_module->mock(
36     'send_or_die',
37     sub {
38         ( $email_object, $sendmail_params ) = @_;
39         my $str = $email_object->email->as_string;
40         unlike $str, qr/I =C3=A2=C2=99=C2=A5 Koha=/, "Content is not double encoded";
41         warn "Fake send_or_die";
42     }
43 );
44
45 use_ok('C4::Context');
46 use_ok('C4::Members');
47 use_ok('C4::Acquisition', qw( NewBasket ));
48 use_ok('C4::Biblio', qw( AddBiblio GetBiblioData ));
49 use_ok('C4::Letters', qw( GetMessageTransportTypes GetMessage EnqueueLetter GetQueuedMessages SendQueuedMessages ResendMessage GetLetters GetPreparedLetter SendAlerts ));
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 # GetPreparedLetter
216 t::lib::Mocks::mock_preference('OPACBaseURL', 'http://thisisatest.com');
217 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
218
219 my $sms_content = 'This is a SMS for an <<status>>';
220 $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 );
221
222 my $tables = {
223     borrowers => $borrowernumber,
224     branches => $library->{branchcode},
225     biblio => $biblionumber,
226 };
227 my $substitute = {
228     status => 'overdue',
229 };
230 my $repeat = [
231     {
232         itemcallnumber => 'my callnumber1',
233         barcode        => '1234',
234     },
235     {
236         itemcallnumber => 'my callnumber2',
237         barcode        => '5678',
238     },
239 ];
240 my $prepared_letter = GetPreparedLetter((
241     module      => 'my module',
242     branchcode  => $library->{branchcode},
243     letter_code => 'my code',
244     tables      => $tables,
245     substitute  => $substitute,
246     repeat      => $repeat,
247 ));
248 my $retrieved_library = Koha::Libraries->find($library->{branchcode});
249 my $my_title_letter = $retrieved_library->branchname . qq| - $substitute->{status}|;
250 my $biblio_timestamp = dt_from_string( GetBiblioData($biblionumber)->{timestamp} );
251 my $my_content_letter = qq|Dear Jane Smith,
252 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.
253
254 |.$retrieved_library->branchname.qq|
255 |.$retrieved_library->branchaddress1.qq|
256 URL: http://thisisatest.com
257
258 The following item(s) is/are currently $substitute->{status}:
259
260 <item> 1. $repeat->[0]->{itemcallnumber}, Barcode: $repeat->[0]->{barcode} </item>
261 <item> 2. $repeat->[1]->{itemcallnumber}, Barcode: $repeat->[1]->{barcode} </item>
262
263 Thank-you for your prompt attention to this matter.
264 Don't forget your date of birth: | . output_pref({ dt => $date, dateonly => 1 }) . q|.
265 Look at this wonderful biblio timestamp: | . output_pref({ dt => $biblio_timestamp })  . ".\n";
266
267 is( $prepared_letter->{title}, $my_title_letter, 'GetPreparedLetter returns the title correctly' );
268 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
269
270 $prepared_letter = GetPreparedLetter((
271     module                 => 'my module',
272     branchcode             => $library->{branchcode},
273     letter_code            => 'my code',
274     tables                 => $tables,
275     substitute             => $substitute,
276     repeat                 => $repeat,
277     message_transport_type => 'sms',
278 ));
279 $my_content_letter = qq|This is a SMS for an $substitute->{status}|;
280 is( $prepared_letter->{content}, $my_content_letter, 'GetPreparedLetter returns the content correctly' );
281
282 warning_is {
283     $prepared_letter = GetPreparedLetter((
284         module                 => 'my module',
285         branchcode             => $library->{branchcode},
286         letter_code            => 'my code',
287         tables                 => $tables,
288         substitute             => { status => undef },
289         repeat                 => $repeat,
290         message_transport_type => 'sms',
291     ));
292 }
293 undef, "No warning if GetPreparedLetter called with substitute containing undefined value";
294 is( $prepared_letter->{content}, q|This is a SMS for an |, 'GetPreparedLetter returns the content correctly when substitute contains undefined value' );
295
296 $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>>.');});
297 $prepared_letter = GetPreparedLetter((
298     module                 => 'test_date',
299     branchcode             => '',
300     letter_code            => 'test_date',
301     tables                 => $tables,
302     substitute             => $substitute,
303     repeat                 => $repeat,
304 ));
305 is( $prepared_letter->{content}, q|This one only contains the date: | . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 1' );
306
307 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp | dateonly>>.' WHERE code = 'test_date';});
308 $prepared_letter = GetPreparedLetter((
309     module                 => 'test_date',
310     branchcode             => '',
311     letter_code            => 'test_date',
312     tables                 => $tables,
313     substitute             => $substitute,
314     repeat                 => $repeat,
315 ));
316 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 2' );
317
318 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp|dateonly >>.' WHERE code = 'test_date';});
319 $prepared_letter = GetPreparedLetter((
320     module                 => 'test_date',
321     branchcode             => '',
322     letter_code            => 'test_date',
323     tables                 => $tables,
324     substitute             => $substitute,
325     repeat                 => $repeat,
326 ));
327 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $date, dateonly => 1 }) . q|.|, 'dateonly test 3' );
328
329 t::lib::Mocks::mock_preference( 'TimeFormat', '12hr' );
330 my $yesterday_night = $date->clone->add( days => -1 )->set_hour(22);
331 $dbh->do(q|UPDATE biblio SET timestamp = ? WHERE biblionumber = ?|, undef, $yesterday_night, $biblionumber );
332 $dbh->do(q{UPDATE letter SET content = 'And also this one:<<timestamp>>.' WHERE code = 'test_date';});
333 $prepared_letter = GetPreparedLetter((
334     module                 => 'test_date',
335     branchcode             => '',
336     letter_code            => 'test_date',
337     tables                 => $tables,
338     substitute             => $substitute,
339     repeat                 => $repeat,
340 ));
341 is( $prepared_letter->{content}, q|And also this one:| . output_pref({ dt => $yesterday_night }) . q|.|, 'dateonly test 3' );
342
343 $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>');});
344 $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> Basket name: [% basket.basketname %]');});
345
346 my $testacqorder2_content = <<EOF;
347 [%- USE Price -%]
348 [% bookseller.name %]
349 [% FOREACH order IN orders %]
350 Ordernumber [% order.ordernumber %] [% order.quantity %] [% order.listprice | \$Price %]
351 [% END %]
352 EOF
353
354 $dbh->do("INSERT INTO letter (module, code, name, title, content) VALUES ('orderacquisition','TESTACQORDER2','Acquisition Order','Order','$testacqorder2_content');");
355
356 my $popito = $builder->build({
357     source => 'Aqbookseller',
358     value  => { name => 'Popito' }
359 });
360
361 my $order_1 = $builder->build({
362     source => 'Aqorder',
363     value  => {
364        quantity => 2,
365        listprice => '12.00'
366     }
367 });
368
369 my $order_2 = $builder->build({
370     source => 'Aqorder',
371     value  => {
372        quantity => 1,
373        listprice => '23.50'
374     }
375 });
376
377 $prepared_letter = GetPreparedLetter((
378     module                 => 'orderacquisition',
379     branchcode             => '',
380     letter_code            => 'TESTACQORDER2',
381     tables                 => { 'aqbooksellers' => $popito->{id} },
382     loops                  => {
383         aqorders => [ $order_1->{ordernumber}, $order_2->{ordernumber} ]
384     }
385 ));
386
387 my $testacqorder2_expected = qq|Popito
388
389 Ordernumber | . $order_1->{ordernumber} . qq| 2 12.00
390
391 Ordernumber | . $order_2->{ordernumber} . qq| 1 23.50
392
393 |;
394
395 is($prepared_letter->{content}, $testacqorder2_expected);
396
397 # Test that _parseletter doesn't modify its parameters bug 15429
398 {
399     my $values = { dateexpiry => '2015-12-13', };
400     C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
401     is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
402 }
403
404 # Correctly format dateexpiry
405 {
406     my $values = { dateexpiry => '2015-12-13', };
407
408     t::lib::Mocks::mock_preference('dateformat', 'metric');
409     t::lib::Mocks::mock_preference('timeformat', '24hr');
410     my $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
411     is( $letter->{content}, 'expiry on 13/12/2015' );
412
413     t::lib::Mocks::mock_preference('dateformat', 'metric');
414     t::lib::Mocks::mock_preference('timeformat', '12hr');
415     $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
416     is( $letter->{content}, 'expiry on 13/12/2015' );
417 }
418
419 my $bookseller = Koha::Acquisition::Bookseller->new(
420     {
421         name => "my vendor",
422         address1 => "bookseller's address",
423         phone => "0123456",
424         active => 1,
425         deliverytime => 5,
426     }
427 )->store;
428 my $booksellerid = $bookseller->id;
429
430 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith',  phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
431 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues      => 1, booksellerid => $booksellerid } )->store;
432 my $basketno = NewBasket($booksellerid, 1, 'The basket name');
433
434 my $budgetid = C4::Budgets::AddBudget({
435     budget_code => "budget_code_test_letters",
436     budget_name => "budget_name_test_letters",
437 });
438
439 my $bib = MARC::Record->new();
440 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
441     $bib->append_fields(
442         MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
443     );
444 } else {
445     $bib->append_fields(
446         MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
447     );
448 }
449
450 my $logged_in_user = $builder->build_object(
451     {
452         class => 'Koha::Patrons',
453         value => {
454             branchcode => $library->{branchcode},
455             email      => 'some@email.com'
456         }
457     }
458 );
459
460 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
461
462 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
463 my $order = Koha::Acquisition::Order->new(
464     {
465         basketno => $basketno,
466         quantity => 1,
467         biblionumber => $biblionumber,
468         budget_id => $budgetid,
469     }
470 )->store;
471 my $ordernumber = $order->ordernumber;
472
473 Koha::Acquisition::Baskets->find( $basketno )->close;
474 my $err;
475 warning_like {
476     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
477     qr/^Bookseller .* without emails at/,
478     "SendAlerts prints a warning";
479 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
480
481 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
482 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
483
484 # Ensure that the preference 'ClaimsLog' is set to logging
485 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
486
487 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
488 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
489
490 {
491 warning_like {
492     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
493     qr|Fake send_or_die|,
494     "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
495 is($err, 1, "Successfully sent order.");
496 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent order");
497 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered) Basket name: The basket name', 'Order notice text constructed successfully');
498
499 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
500 $mocked_koha_email->mock( 'send_or_die', sub {
501     Email::Sender::Failure->throw('something went wrong');
502 });
503
504 warning_like {
505     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ); }
506     qr{something went wrong},
507     'Warning is printed';
508
509 is($err->{error}, 'something went wrong', "Send exception, error message returned");
510
511 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
512 warning_like {
513     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
514     qr/No orderacquisition TESTACQORDER letter transported by email/,
515     "GetPreparedLetter warns about missing notice template";
516 is($err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined.");
517 }
518
519 {
520 warning_like {
521     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
522     qr|Fake send_or_die|,
523     "SendAlerts is using the mocked send_or_die routine";
524
525 is($err, 1, "Successfully sent claim");
526 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
527 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
528
529 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
530 $mocked_koha_email->mock( 'send_or_die', sub {
531     Email::Sender::Failure->throw('something went wrong');
532 });
533
534 warning_like {
535     $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
536     qr{something went wrong},
537     'Warning is printed';
538
539 is($err->{error}, 'something went wrong', "Send exception, error message returned");
540 }
541
542 {
543 use C4::Serials qw( NewSubscription GetSerials findSerialsByStatus ModSerialStatus );
544
545 my $notes = 'notes';
546 my $internalnotes = 'intnotes';
547 my $number_pattern = $builder->build_object(
548     {
549         class => 'Koha::Subscription::Numberpatterns',
550         value => { numberingmethod => 'No. {X}' }
551     }
552 );
553 my $subscriptionid = NewSubscription(
554      undef,      "",     undef, undef, undef, $biblionumber,
555     '2013-01-01', 1, undef, undef,  undef,
556     undef,      undef,  undef, undef, undef, undef,
557     1,          $notes,undef, '2013-01-01', undef, $number_pattern->id,
558     undef,       undef,  0,    $internalnotes,  0,
559     undef, undef, 0,          undef,         '2013-12-31', 0
560 );
561 $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>>');});
562 my ($serials_count, @serials) = GetSerials($subscriptionid);
563 my $serial = $serials[0];
564
565 my $patron = Koha::Patron->new({
566     firstname    => 'John',
567     surname      => 'Smith',
568     categorycode => $patron_category,
569     branchcode   => $library->{branchcode},
570     dateofbirth  => $date,
571     email        => 'john.smith@test.de',
572 })->store;
573 my $borrowernumber = $patron->borrowernumber;
574 my $subscription = Koha::Subscriptions->find( $subscriptionid );
575 $subscription->add_subscriber( $patron );
576
577 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
578 my $err2;
579 warning_like {
580 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
581     qr|Fake send_or_die|,
582     "SendAlerts is using the mocked send_or_die routine";
583
584 is($err2, 1, "Successfully sent serial notification");
585 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
586 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
587
588 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
589
590 my $err3;
591 warning_like {
592 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
593     qr|Fake send_or_die|,
594     "SendAlerts is using the mocked send_or_die routine";
595 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
596
597 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
598 $mocked_koha_email->mock( 'send_or_die', sub {
599     Email::Sender::Failure->throw('something went wrong');
600 });
601
602 warning_like {
603     $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
604     qr{something went wrong},
605     'Warning is printed';
606
607 is($err->{error}, 'something went wrong', "Send exception, error message returned");
608
609 }
610 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
611
612 subtest 'SendAlerts - claimissue' => sub {
613     plan tests => 13;
614
615     use C4::Serials;
616
617     $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>>');});
618
619     my $bookseller = Koha::Acquisition::Bookseller->new(
620         {
621             name => "my vendor",
622             address1 => "bookseller's address",
623             phone => "0123456",
624             active => 1,
625             deliverytime => 5,
626         }
627     )->store;
628     my $booksellerid = $bookseller->id;
629
630     Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
631
632     my $bib = MARC::Record->new();
633     if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
634         $bib->append_fields(
635             MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
636             MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
637         );
638     } else {
639         $bib->append_fields(
640             MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
641             MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
642         );
643     }
644     my ($biblionumber) = AddBiblio($bib, '');
645
646     my $number_pattern = $builder->build_object(
647         {
648             class => 'Koha::Subscription::Numberpatterns',
649             value => { numberingmethod => 'No. {X}' }
650         }
651     );
652
653     my $subscriptionid = NewSubscription(
654          undef, "", $booksellerid, undef, undef, $biblionumber,
655         '2013-01-01', 1, undef, undef,  undef,
656         undef,  undef,  undef, undef, undef, undef,
657         1, 'public',undef, '2013-01-01', undef, $number_pattern->id,
658         undef, undef,  0, 'internal',  0,
659         undef, undef, 0,  undef, '2013-12-31', 0
660     );
661
662     my ($serials_count, @serials) = GetSerials($subscriptionid);
663     my  @serialids = ($serials[0]->{serialid});
664
665     my $err;
666     warning_like {
667         $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
668         qr/^Bookseller .* without emails at/,
669         "Warn on vendor without email address";
670
671     $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
672     $bookseller->contacts->next->email('testemail@mydomain.com')->store;
673
674     # Ensure that the preference 'ClaimsLog' is set to logging
675     t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
676
677     # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
678     t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
679
680     t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
681
682     {
683     warning_like {
684         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
685         qr|Fake send_or_die|,
686         "SendAlerts is using the mocked send_or_die routine (claimissues)";
687     is( $err, 1, "Successfully sent claim" );
688     is( $email_object->email->header('To'),
689         'testemail@mydomain.com', "mailto correct in sent claim" );
690     is(
691         $email_object->email->body,
692         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
693         'Serial claim letter for 1 issue constructed successfully'
694     );
695
696     my $mocked_koha_email = Test::MockModule->new('Koha::Email');
697     $mocked_koha_email->mock( 'send_or_die', sub {
698             Email::Sender::Failure->throw('something went wrong');
699     });
700
701     warning_like {
702         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
703         qr{something went wrong},
704         'Warning is printed';
705
706     is($err->{error}, 'something went wrong', "Send exception, error message returned");
707     }
708
709     {
710     my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
711     my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
712     ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
713     ($serials_count, @serials) = GetSerials($subscriptionid);
714     push @serialids, ($serials[1]->{serialid});
715
716     warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
717         qr|Fake send_or_die|,
718         "SendAlerts is using the mocked send_or_die routine (claimissues)";
719
720     is(
721         $email_object->email->body,
722         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
723           . $email_object->email->crlf
724           . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
725         "Serial claim letter for 2 issues constructed successfully"
726     );
727
728     $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
729     warning_like {
730         $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
731         qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
732         "GetPreparedLetter warns about missing notice template";
733     is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
734     }
735
736 };
737
738 subtest 'GetPreparedLetter' => sub {
739     plan tests => 4;
740
741     Koha::Notice::Template->new(
742         {
743             module                 => 'test',
744             code                   => 'test',
745             branchcode             => '',
746             message_transport_type => 'email'
747         }
748     )->store;
749     my $letter;
750     warning_like {
751         $letter = C4::Letters::GetPreparedLetter(
752             module      => 'test',
753             letter_code => 'test',
754         );
755     }
756     qr{^ERROR: nothing to substitute},
757 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
758     is( $letter, undef,
759 'No letter should be returned by GetPreparedLetter if something went wrong'
760     );
761
762     warning_like {
763         $letter = C4::Letters::GetPreparedLetter(
764             module      => 'test',
765             letter_code => 'test',
766             substitute  => {}
767         );
768     }
769     qr{^ERROR: nothing to substitute},
770 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
771     is( $letter, undef,
772 'No letter should be returned by GetPreparedLetter if something went wrong'
773     );
774
775 };
776
777
778
779 subtest 'TranslateNotices' => sub {
780     plan tests => 4;
781
782     t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
783
784     $dbh->do(
785         q|
786         INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
787         ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
788         ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
789     | );
790     my $substitute = {};
791     my $letter = C4::Letters::GetPreparedLetter(
792             module                 => 'test',
793             tables                 => $tables,
794             letter_code            => 'code',
795             message_transport_type => 'email',
796             substitute             => $substitute,
797     );
798     is(
799         $letter->{title},
800         'a test',
801         'GetPreparedLetter should return the default one if the lang parameter is not provided'
802     );
803
804     $letter = C4::Letters::GetPreparedLetter(
805             module                 => 'test',
806             tables                 => $tables,
807             letter_code            => 'code',
808             message_transport_type => 'email',
809             substitute             => $substitute,
810             lang                   => 'es-ES',
811     );
812     is( $letter->{title}, 'una prueba',
813         'GetPreparedLetter should return the required notice if it exists' );
814
815     $letter = C4::Letters::GetPreparedLetter(
816             module                 => 'test',
817             tables                 => $tables,
818             letter_code            => 'code',
819             message_transport_type => 'email',
820             substitute             => $substitute,
821             lang                   => 'fr-FR',
822     );
823     is(
824         $letter->{title},
825         'a test',
826         'GetPreparedLetter should return the default notice if the one required does not exist'
827     );
828
829     t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
830
831     $letter = C4::Letters::GetPreparedLetter(
832             module                 => 'test',
833             tables                 => $tables,
834             letter_code            => 'code',
835             message_transport_type => 'email',
836             substitute             => $substitute,
837             lang                   => 'es-ES',
838     );
839     is( $letter->{title}, 'a test',
840         'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
841
842 };
843
844 subtest 'Test SMS handling in SendQueuedMessages' => sub {
845
846     plan tests => 14;
847
848     t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
849     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
850
851     my $patron = Koha::Patrons->find($borrowernumber);
852     $dbh->do(q|
853         INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
854         VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
855         |, undef, $borrowernumber
856     );
857     eval { C4::Letters::SendQueuedMessages(); };
858     is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
859
860     my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
861     $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
862     $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
863
864     warning_like { C4::Letters::SendQueuedMessages(); }
865         qr|Fake send_or_die|,
866         "SendAlerts is using the mocked send_or_die routine (claimissues)";
867
868     my $message = $schema->resultset('MessageQueue')->search({
869         borrowernumber => $borrowernumber,
870         status => 'sent'
871     })->next();
872
873     is( $message->letter_id, $messages->[0]->{id}, "Message letter_id is set correctly" );
874     is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
875     is(
876         $message->from_address(),
877         'from@example.com',
878         'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
879     );
880
881     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
882
883     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
884
885     $message_id = C4::Letters::EnqueueLetter($my_message);
886     warning_like { C4::Letters::SendQueuedMessages(); }
887         qr|Fake send_or_die|,
888         "SendAlerts is using the mocked send_or_die routine (claimissues)";
889
890     $message = $schema->resultset('MessageQueue')->search({
891         borrowernumber => $borrowernumber,
892         status => 'sent'
893     })->next();
894
895     is(
896         $message->from_address(),
897         'override@example.com',
898         'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
899     );
900
901     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
902     $my_message->{to_address} = 'fixme@kidclamp.iswrong';
903     $message_id = C4::Letters::EnqueueLetter($my_message);
904
905     my $number_attempted = C4::Letters::SendQueuedMessages({
906         borrowernumber => -1, # -1 still triggers the borrowernumber condition
907         letter_code    => 'PASSWORD_RESET',
908     });
909     is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
910
911     warning_like { C4::Letters::SendQueuedMessages(); }
912         qr|Fake send_or_die|,
913         "SendAlerts is using the mocked send_or_die routine (claimissues)";
914
915     my $sms_message_address = $schema->resultset('MessageQueue')->search({
916         borrowernumber => $borrowernumber,
917         status => 'sent'
918     })->next()->to_address();
919     is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
920
921     # Test using SMS::Send::Test driver that's bundled with SMS::Send
922     t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
923
924     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
925     C4::Letters::EnqueueLetter($my_message);
926     C4::Letters::SendQueuedMessages();
927
928     $sms_message_address = $schema->resultset('MessageQueue')->search({
929         borrowernumber => $borrowernumber,
930         status => 'sent'
931     })->next()->to_address();
932     is( $sms_message_address, '5555555555', 'SendQueuedMessages populates the to address correctly for SMS by SMS::Send driver to smsalertnumber when to_address is set incorrectly' );
933
934 };
935
936 subtest 'get_item_content' => sub {
937     plan tests => 2;
938
939     t::lib::Mocks::mock_preference('dateformat', 'metric');
940     t::lib::Mocks::mock_preference('timeformat', '24hr');
941     my @items = (
942         {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
943         {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
944     );
945     my @item_content_fields = qw( date_due title barcode author itemnumber );
946
947     my $items_content;
948     for my $item ( @items ) {
949         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
950     }
951
952     my $expected_items_content = <<EOF;
953 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
954 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
955 EOF
956     is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
957
958
959     $items_content = q||;
960     for my $item ( @items ) {
961         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
962     }
963
964     $expected_items_content = <<EOF;
965 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
966 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
967 EOF
968     is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
969 };
970
971 subtest 'Test limit parameter for SendQueuedMessages' => sub {
972     plan tests => 3;
973
974     my $dbh = C4::Context->dbh;
975
976     my $borrowernumber = Koha::Patron->new({
977         firstname    => 'Jane',
978         surname      => 'Smith',
979         categorycode => $patron_category,
980         branchcode   => $library->{branchcode},
981         dateofbirth  => $date,
982         smsalertnumber => undef,
983     })->store->borrowernumber;
984
985     $dbh->do(q|DELETE FROM message_queue|);
986     $my_message = {
987         'letter' => {
988             'content'      => 'a message',
989             'metadata'     => 'metadata',
990             'code'         => 'TEST_MESSAGE',
991             'content_type' => 'text/plain',
992             'title'        => 'message title'
993         },
994         'borrowernumber'         => $borrowernumber,
995         'to_address'             => undef,
996         'message_transport_type' => 'sms',
997         'from_address'           => 'from@example.com'
998     };
999     C4::Letters::EnqueueLetter($my_message);
1000     C4::Letters::EnqueueLetter($my_message);
1001     C4::Letters::EnqueueLetter($my_message);
1002     C4::Letters::EnqueueLetter($my_message);
1003     C4::Letters::EnqueueLetter($my_message);
1004     my $messages_processed = C4::Letters::SendQueuedMessages( { limit => 1 } );
1005     is( $messages_processed, 1,
1006         'Processed 1 message with limit of 1 and 5 unprocessed messages' );
1007     $messages_processed = C4::Letters::SendQueuedMessages( { limit => 2 } );
1008     is( $messages_processed, 2,
1009         'Processed 2 message with limit of 2 and 4 unprocessed messages' );
1010     $messages_processed = C4::Letters::SendQueuedMessages( { limit => 3 } );
1011     is( $messages_processed, 2,
1012         'Processed 2 message with limit of 3 and 2 unprocessed messages' );
1013 };
1014
1015 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
1016
1017     plan tests => 7;
1018
1019     my $dbh = C4::Context->dbh;
1020
1021     my $borrowernumber = Koha::Patron->new({
1022         firstname    => 'Jane',
1023         surname      => 'Smith',
1024         categorycode => $patron_category,
1025         branchcode   => $library->{branchcode},
1026         dateofbirth  => $date,
1027         smsalertnumber => undef,
1028     })->store->borrowernumber;
1029
1030     $dbh->do(q|DELETE FROM message_queue|);
1031     $my_message = {
1032         'letter' => {
1033             'content'      => 'a message',
1034             'metadata'     => 'metadata',
1035             'code'         => 'TEST_MESSAGE',
1036             'content_type' => 'text/plain',
1037             'title'        => 'message title'
1038         },
1039         'borrowernumber'         => $borrowernumber,
1040         'to_address'             => 'to@example.org',
1041         'message_transport_type' => 'email',
1042         'from_address'           => '@example.com' # invalid from_address
1043     };
1044     my $message_id = C4::Letters::EnqueueLetter($my_message);
1045     my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
1046     is( $processed, 1, 'Processed 1 message when one message_id passed' );
1047     my $message_1 = C4::Letters::GetMessage($message_id);
1048     is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
1049     is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
1050
1051     $my_message->{from_address} = 'root@example.org'; # valid from_address
1052     $message_id = C4::Letters::EnqueueLetter($my_message);
1053     warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
1054         qr|Fake send_or_die|,
1055         "SendQueuedMessages is using the mocked send_or_die routine";
1056     $message_1 = C4::Letters::GetMessage($message_1->{message_id});
1057     my $message_2 = C4::Letters::GetMessage($message_id);
1058     is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
1059     is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
1060 };