Bug 30921: Replace C4::XSLT::transformMARCXML4XSLT with RecordProcessor
[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 => 85;
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>');});
345
346 # Test that _parseletter doesn't modify its parameters bug 15429
347 {
348     my $values = { dateexpiry => '2015-12-13', };
349     C4::Letters::_parseletter($prepared_letter, 'borrowers', $values);
350     is( $values->{dateexpiry}, '2015-12-13', "_parseletter doesn't modify its parameters" );
351 }
352
353 # Correctly format dateexpiry
354 {
355     my $values = { dateexpiry => '2015-12-13', };
356
357     t::lib::Mocks::mock_preference('dateformat', 'metric');
358     t::lib::Mocks::mock_preference('timeformat', '24hr');
359     my $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
360     is( $letter->{content}, 'expiry on 13/12/2015' );
361
362     t::lib::Mocks::mock_preference('dateformat', 'metric');
363     t::lib::Mocks::mock_preference('timeformat', '12hr');
364     $letter = C4::Letters::_parseletter({ content => "expiry on <<borrowers.dateexpiry>>"}, 'borrowers', $values);
365     is( $letter->{content}, 'expiry on 13/12/2015' );
366 }
367
368 my $bookseller = Koha::Acquisition::Bookseller->new(
369     {
370         name => "my vendor",
371         address1 => "bookseller's address",
372         phone => "0123456",
373         active => 1,
374         deliverytime => 5,
375     }
376 )->store;
377 my $booksellerid = $bookseller->id;
378
379 Koha::Acquisition::Bookseller::Contact->new( { name => 'John Smith',  phone => '0123456x1', claimacquisition => 1, orderacquisition => 1, booksellerid => $booksellerid } )->store;
380 Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues      => 1, booksellerid => $booksellerid } )->store;
381 my $basketno = NewBasket($booksellerid, 1);
382
383 my $budgetid = C4::Budgets::AddBudget({
384     budget_code => "budget_code_test_letters",
385     budget_name => "budget_name_test_letters",
386 });
387
388 my $bib = MARC::Record->new();
389 if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
390     $bib->append_fields(
391         MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
392     );
393 } else {
394     $bib->append_fields(
395         MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
396     );
397 }
398
399 my $logged_in_user = $builder->build_object(
400     {
401         class => 'Koha::Patrons',
402         value => {
403             branchcode => $library->{branchcode},
404             email      => 'some@email.com'
405         }
406     }
407 );
408
409 t::lib::Mocks::mock_userenv({ patron => $logged_in_user });
410
411 ($biblionumber, $biblioitemnumber) = AddBiblio($bib, '');
412 my $order = Koha::Acquisition::Order->new(
413     {
414         basketno => $basketno,
415         quantity => 1,
416         biblionumber => $biblionumber,
417         budget_id => $budgetid,
418     }
419 )->store;
420 my $ordernumber = $order->ordernumber;
421
422 Koha::Acquisition::Baskets->find( $basketno )->close;
423 my $err;
424 warning_like {
425     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
426     qr/^Bookseller .* without emails at/,
427     "SendAlerts prints a warning";
428 is($err->{'error'}, 'no_email', "Trying to send an alert when there's no e-mail results in an error");
429
430 $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
431 $bookseller->contacts->next->email('testemail@mydomain.com')->store;
432
433 # Ensure that the preference 'ClaimsLog' is set to logging
434 t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
435
436 # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
437 t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
438
439 {
440 warning_like {
441     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
442     qr|Fake send_or_die|,
443     "SendAlerts is using the mocked send_or_die routine (orderacquisition)";
444 is($err, 1, "Successfully sent order.");
445 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent order");
446 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Order notice text constructed successfully');
447
448 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
449 $mocked_koha_email->mock( 'send_or_die', sub {
450     Email::Sender::Failure->throw('something went wrong');
451 });
452
453 warning_like {
454     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ); }
455     qr{something went wrong},
456     'Warning is printed';
457
458 is($err->{error}, 'something went wrong', "Send exception, error message returned");
459
460 $dbh->do(q{DELETE FROM letter WHERE code = 'TESTACQORDER';});
461 warning_like {
462     $err = SendAlerts( 'orderacquisition', $basketno , 'TESTACQORDER' ) }
463     qr/No orderacquisition TESTACQORDER letter transported by email/,
464     "GetPreparedLetter warns about missing notice template";
465 is($err->{'error'}, 'no_letter', "No TESTACQORDER letter was defined.");
466 }
467
468 {
469 warning_like {
470     $err = SendAlerts( 'claimacquisition', [ $ordernumber ], 'TESTACQCLAIM' ) }
471     qr|Fake send_or_die|,
472     "SendAlerts is using the mocked send_or_die routine";
473
474 is($err, 1, "Successfully sent claim");
475 is($email_object->email->header('To'), 'testemail@mydomain.com', "mailto correct in sent claim");
476 is($email_object->email->body, 'my vendor|John Smith|Ordernumber ' . $ordernumber . ' (Silence in the library) (1 ordered)', 'Claim notice text constructed successfully');
477
478 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
479 $mocked_koha_email->mock( 'send_or_die', sub {
480     Email::Sender::Failure->throw('something went wrong');
481 });
482
483 warning_like {
484     $err = SendAlerts( 'claimacquisition', [ $ordernumber ] , 'TESTACQCLAIM' ); }
485     qr{something went wrong},
486     'Warning is printed';
487
488 is($err->{error}, 'something went wrong', "Send exception, error message returned");
489 }
490
491 {
492 use C4::Serials qw( NewSubscription GetSerials findSerialsByStatus ModSerialStatus );
493
494 my $notes = 'notes';
495 my $internalnotes = 'intnotes';
496 $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
497 my $subscriptionid = NewSubscription(
498      undef,      "",     undef, undef, undef, $biblionumber,
499     '2013-01-01', 1, undef, undef,  undef,
500     undef,      undef,  undef, undef, undef, undef,
501     1,          $notes,undef, '2013-01-01', undef, 1,
502     undef,       undef,  0,    $internalnotes,  0,
503     undef, undef, 0,          undef,         '2013-12-31', 0
504 );
505 $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>>');});
506 my ($serials_count, @serials) = GetSerials($subscriptionid);
507 my $serial = $serials[0];
508
509 my $patron = Koha::Patron->new({
510     firstname    => 'John',
511     surname      => 'Smith',
512     categorycode => $patron_category,
513     branchcode   => $library->{branchcode},
514     dateofbirth  => $date,
515     email        => 'john.smith@test.de',
516 })->store;
517 my $borrowernumber = $patron->borrowernumber;
518 my $subscription = Koha::Subscriptions->find( $subscriptionid );
519 $subscription->add_subscriber( $patron );
520
521 t::lib::Mocks::mock_userenv({ branch => $library->{branchcode} });
522 my $err2;
523 warning_like {
524 $err2 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
525     qr|Fake send_or_die|,
526     "SendAlerts is using the mocked send_or_die routine";
527
528 is($err2, 1, "Successfully sent serial notification");
529 is($email_object->email->header('To'), 'john.smith@test.de', "mailto correct in sent serial notification");
530 is($email_object->email->body, 'Silence in the library,'.$subscriptionid.',No. 0', 'Serial notification text constructed successfully');
531
532 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', 'robert.tables@mail.com' );
533
534 my $err3;
535 warning_like {
536 $err3 = SendAlerts( 'issue', $serial->{serialid}, 'RLIST' ) }
537     qr|Fake send_or_die|,
538     "SendAlerts is using the mocked send_or_die routine";
539 is($email_object->email->header('To'), 'robert.tables@mail.com', "mailto address overwritten by SendAllMailsTo preference");
540
541 my $mocked_koha_email = Test::MockModule->new('Koha::Email');
542 $mocked_koha_email->mock( 'send_or_die', sub {
543     Email::Sender::Failure->throw('something went wrong');
544 });
545
546 warning_like {
547     $err = SendAlerts( 'issue', $serial->{serialid} , 'RLIST' ); }
548     qr{something went wrong},
549     'Warning is printed';
550
551 is($err->{error}, 'something went wrong', "Send exception, error message returned");
552
553 }
554 t::lib::Mocks::mock_preference( 'SendAllEmailsTo', '' );
555
556 subtest 'SendAlerts - claimissue' => sub {
557     plan tests => 13;
558
559     use C4::Serials;
560
561     $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>>');});
562
563     my $bookseller = Koha::Acquisition::Bookseller->new(
564         {
565             name => "my vendor",
566             address1 => "bookseller's address",
567             phone => "0123456",
568             active => 1,
569             deliverytime => 5,
570         }
571     )->store;
572     my $booksellerid = $bookseller->id;
573
574     Koha::Acquisition::Bookseller::Contact->new( { name => 'Leo Tolstoy', phone => '0123456x2', claimissues => 1, booksellerid => $booksellerid } )->store;
575
576     my $bib = MARC::Record->new();
577     if (C4::Context->preference('marcflavour') eq 'UNIMARC') {
578         $bib->append_fields(
579             MARC::Field->new('011', ' ', ' ', a => 'xxxx-yyyy'),
580             MARC::Field->new('200', ' ', ' ', a => 'Silence in the library'),
581         );
582     } else {
583         $bib->append_fields(
584             MARC::Field->new('022', ' ', ' ', a => 'xxxx-yyyy'),
585             MARC::Field->new('245', ' ', ' ', a => 'Silence in the library'),
586         );
587     }
588     my ($biblionumber) = AddBiblio($bib, '');
589
590     $dbh->do(q|UPDATE subscription_numberpatterns SET numberingmethod='No. {X}' WHERE id=1|);
591     my $subscriptionid = NewSubscription(
592          undef, "", $booksellerid, undef, undef, $biblionumber,
593         '2013-01-01', 1, undef, undef,  undef,
594         undef,  undef,  undef, undef, undef, undef,
595         1, 'public',undef, '2013-01-01', undef, 1,
596         undef, undef,  0, 'internal',  0,
597         undef, undef, 0,  undef, '2013-12-31', 0
598     );
599
600     my ($serials_count, @serials) = GetSerials($subscriptionid);
601     my  @serialids = ($serials[0]->{serialid});
602
603     my $err;
604     warning_like {
605         $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ) }
606         qr/^Bookseller .* without emails at/,
607         "Warn on vendor without email address";
608
609     $bookseller = Koha::Acquisition::Booksellers->find( $booksellerid );
610     $bookseller->contacts->next->email('testemail@mydomain.com')->store;
611
612     # Ensure that the preference 'ClaimsLog' is set to logging
613     t::lib::Mocks::mock_preference( 'ClaimsLog', 'on' );
614
615     # SendAlerts needs branchemail or KohaAdminEmailAddress as sender
616     t::lib::Mocks::mock_userenv({ branchcode => $library->{branchcode} });
617
618     t::lib::Mocks::mock_preference( 'KohaAdminEmailAddress', 'library@domain.com' );
619
620     {
621     warning_like {
622         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ) }
623         qr|Fake send_or_die|,
624         "SendAlerts is using the mocked send_or_die routine (claimissues)";
625     is( $err, 1, "Successfully sent claim" );
626     is( $email_object->email->header('To'),
627         'testemail@mydomain.com', "mailto correct in sent claim" );
628     is(
629         $email_object->email->body,
630         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy",
631         'Serial claim letter for 1 issue constructed successfully'
632     );
633
634     my $mocked_koha_email = Test::MockModule->new('Koha::Email');
635     $mocked_koha_email->mock( 'send_or_die', sub {
636             Email::Sender::Failure->throw('something went wrong');
637     });
638
639     warning_like {
640         $err = SendAlerts( 'claimissues', \@serialids , 'TESTSERIALCLAIM' ); }
641         qr{something went wrong},
642         'Warning is printed';
643
644     is($err->{error}, 'something went wrong', "Send exception, error message returned");
645     }
646
647     {
648     my $publisheddate = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
649     my $serialexpected = ( C4::Serials::findSerialsByStatus( 1, $subscriptionid ) )[0];
650     ModSerialStatus( $serials[0]->{serialid}, "No. 1", $publisheddate, $publisheddate, $publisheddate, '3', 'a note' );
651     ($serials_count, @serials) = GetSerials($subscriptionid);
652     push @serialids, ($serials[1]->{serialid});
653
654     warning_like { $err = SendAlerts( 'claimissues', \@serialids, 'TESTSERIALCLAIM' ); }
655         qr|Fake send_or_die|,
656         "SendAlerts is using the mocked send_or_die routine (claimissues)";
657
658     is(
659         $email_object->email->body,
660         "$serialids[0]|2013-01-01|Silence in the library|xxxx-yyyy"
661           . $email_object->email->crlf
662           . "$serialids[1]|2013-01-01|Silence in the library|xxxx-yyyy",
663         "Serial claim letter for 2 issues constructed successfully"
664     );
665
666     $dbh->do(q{DELETE FROM letter WHERE code = 'TESTSERIALCLAIM';});
667     warning_like {
668         $err = SendAlerts( 'orderacquisition', $basketno , 'TESTSERIALCLAIM' ) }
669         qr/No orderacquisition TESTSERIALCLAIM letter transported by email/,
670         "GetPreparedLetter warns about missing notice template";
671     is($err->{'error'}, 'no_letter', "No TESTSERIALCLAIM letter was defined");
672     }
673
674 };
675
676 subtest 'GetPreparedLetter' => sub {
677     plan tests => 4;
678
679     Koha::Notice::Template->new(
680         {
681             module                 => 'test',
682             code                   => 'test',
683             branchcode             => '',
684             message_transport_type => 'email'
685         }
686     )->store;
687     my $letter;
688     warning_like {
689         $letter = C4::Letters::GetPreparedLetter(
690             module      => 'test',
691             letter_code => 'test',
692         );
693     }
694     qr{^ERROR: nothing to substitute},
695 'GetPreparedLetter should warn if tables, substiture and repeat are not set';
696     is( $letter, undef,
697 'No letter should be returned by GetPreparedLetter if something went wrong'
698     );
699
700     warning_like {
701         $letter = C4::Letters::GetPreparedLetter(
702             module      => 'test',
703             letter_code => 'test',
704             substitute  => {}
705         );
706     }
707     qr{^ERROR: nothing to substitute},
708 'GetPreparedLetter should warn if tables, substiture and repeat are not set, even if the key is passed';
709     is( $letter, undef,
710 'No letter should be returned by GetPreparedLetter if something went wrong'
711     );
712
713 };
714
715
716
717 subtest 'TranslateNotices' => sub {
718     plan tests => 4;
719
720     t::lib::Mocks::mock_preference( 'TranslateNotices', '1' );
721
722     $dbh->do(
723         q|
724         INSERT INTO letter (module, code, branchcode, name, title, content, message_transport_type, lang) VALUES
725         ('test', 'code', '', 'test', 'a test', 'just a test', 'email', 'default'),
726         ('test', 'code', '', 'test', 'una prueba', 'solo una prueba', 'email', 'es-ES');
727     | );
728     my $substitute = {};
729     my $letter = C4::Letters::GetPreparedLetter(
730             module                 => 'test',
731             tables                 => $tables,
732             letter_code            => 'code',
733             message_transport_type => 'email',
734             substitute             => $substitute,
735     );
736     is(
737         $letter->{title},
738         'a test',
739         'GetPreparedLetter should return the default one if the lang parameter is not provided'
740     );
741
742     $letter = C4::Letters::GetPreparedLetter(
743             module                 => 'test',
744             tables                 => $tables,
745             letter_code            => 'code',
746             message_transport_type => 'email',
747             substitute             => $substitute,
748             lang                   => 'es-ES',
749     );
750     is( $letter->{title}, 'una prueba',
751         'GetPreparedLetter should return the required notice if it exists' );
752
753     $letter = C4::Letters::GetPreparedLetter(
754             module                 => 'test',
755             tables                 => $tables,
756             letter_code            => 'code',
757             message_transport_type => 'email',
758             substitute             => $substitute,
759             lang                   => 'fr-FR',
760     );
761     is(
762         $letter->{title},
763         'a test',
764         'GetPreparedLetter should return the default notice if the one required does not exist'
765     );
766
767     t::lib::Mocks::mock_preference( 'TranslateNotices', '' );
768
769     $letter = C4::Letters::GetPreparedLetter(
770             module                 => 'test',
771             tables                 => $tables,
772             letter_code            => 'code',
773             message_transport_type => 'email',
774             substitute             => $substitute,
775             lang                   => 'es-ES',
776     );
777     is( $letter->{title}, 'a test',
778         'GetPreparedLetter should return the default notice if pref disabled but additional language exists' );
779
780 };
781
782 subtest 'Test SMS handling in SendQueuedMessages' => sub {
783
784     plan tests => 13;
785
786     t::lib::Mocks::mock_preference( 'SMSSendDriver', 'Email' );
787     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', '');
788
789     my $patron = Koha::Patrons->find($borrowernumber);
790     $dbh->do(q|
791         INSERT INTO message_queue(borrowernumber, subject, content, message_transport_type, status, letter_code)
792         VALUES (?, 'subject', 'content', 'sms', 'pending', 'just_a_code')
793         |, undef, $borrowernumber
794     );
795     eval { C4::Letters::SendQueuedMessages(); };
796     is( $@, '', 'SendQueuedMessages should not explode if the patron does not have a sms provider set' );
797
798     my $sms_pro = $builder->build_object({ class => 'Koha::SMS::Providers', value => { domain => 'kidclamp.rocks' } });
799     $patron->set( { smsalertnumber => '5555555555', sms_provider_id => $sms_pro->id() } )->store;
800     $message_id = C4::Letters::EnqueueLetter($my_message); #using datas set around line 95 and forward
801
802     warning_like { C4::Letters::SendQueuedMessages(); }
803         qr|Fake send_or_die|,
804         "SendAlerts is using the mocked send_or_die routine (claimissues)";
805
806     my $message = $schema->resultset('MessageQueue')->search({
807         borrowernumber => $borrowernumber,
808         status => 'sent'
809     })->next();
810
811     is( $message->to_address(), '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address not set' );
812     is(
813         $message->from_address(),
814         'from@example.com',
815         'SendQueuedMessages uses message queue item \"from address\" for SMS by email when EmailSMSSendDriverFromAddress system preference is not set'
816     );
817
818     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
819
820     t::lib::Mocks::mock_preference('EmailSMSSendDriverFromAddress', 'override@example.com');
821
822     $message_id = C4::Letters::EnqueueLetter($my_message);
823     warning_like { C4::Letters::SendQueuedMessages(); }
824         qr|Fake send_or_die|,
825         "SendAlerts is using the mocked send_or_die routine (claimissues)";
826
827     $message = $schema->resultset('MessageQueue')->search({
828         borrowernumber => $borrowernumber,
829         status => 'sent'
830     })->next();
831
832     is(
833         $message->from_address(),
834         'override@example.com',
835         'SendQueuedMessages uses EmailSMSSendDriverFromAddress value for SMS by email when EmailSMSSendDriverFromAddress is set'
836     );
837
838     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber,status => 'sent'})->delete(); #clear borrower queue
839     $my_message->{to_address} = 'fixme@kidclamp.iswrong';
840     $message_id = C4::Letters::EnqueueLetter($my_message);
841
842     my $number_attempted = C4::Letters::SendQueuedMessages({
843         borrowernumber => -1, # -1 still triggers the borrowernumber condition
844         letter_code    => 'PASSWORD_RESET',
845     });
846     is ( $number_attempted, 0, 'There were no password reset messages for SendQueuedMessages to attempt.' );
847
848     warning_like { C4::Letters::SendQueuedMessages(); }
849         qr|Fake send_or_die|,
850         "SendAlerts is using the mocked send_or_die routine (claimissues)";
851
852     my $sms_message_address = $schema->resultset('MessageQueue')->search({
853         borrowernumber => $borrowernumber,
854         status => 'sent'
855     })->next()->to_address();
856     is( $sms_message_address, '5555555555@kidclamp.rocks', 'SendQueuedMessages populates the to address correctly for SMS by email when to_address is set incorrectly' );
857
858     # Test using SMS::Send::Test driver that's bundled with SMS::Send
859     t::lib::Mocks::mock_preference('SMSSendDriver', "AU::Test");
860
861     $schema->resultset('MessageQueue')->search({borrowernumber => $borrowernumber, status => 'sent'})->delete(); #clear borrower queue
862     C4::Letters::EnqueueLetter($my_message);
863     C4::Letters::SendQueuedMessages();
864
865     $sms_message_address = $schema->resultset('MessageQueue')->search({
866         borrowernumber => $borrowernumber,
867         status => 'sent'
868     })->next()->to_address();
869     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' );
870
871 };
872
873 subtest 'get_item_content' => sub {
874     plan tests => 2;
875
876     t::lib::Mocks::mock_preference('dateformat', 'metric');
877     t::lib::Mocks::mock_preference('timeformat', '24hr');
878     my @items = (
879         {date_due => '2041-01-01 12:34', title => 'a first title', barcode => 'a_first_barcode', author => 'a_first_author', itemnumber => 1 },
880         {date_due => '2042-01-02 23:45', title => 'a second title', barcode => 'a_second_barcode', author => 'a_second_author', itemnumber => 2 },
881     );
882     my @item_content_fields = qw( date_due title barcode author itemnumber );
883
884     my $items_content;
885     for my $item ( @items ) {
886         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields } );
887     }
888
889     my $expected_items_content = <<EOF;
890 01/01/2041 12:34\ta first title\ta_first_barcode\ta_first_author\t1
891 02/01/2042 23:45\ta second title\ta_second_barcode\ta_second_author\t2
892 EOF
893     is( $items_content, $expected_items_content, 'get_item_content should return correct items info with time (default)' );
894
895
896     $items_content = q||;
897     for my $item ( @items ) {
898         $items_content .= C4::Letters::get_item_content( { item => $item, item_content_fields => \@item_content_fields, dateonly => 1, } );
899     }
900
901     $expected_items_content = <<EOF;
902 01/01/2041\ta first title\ta_first_barcode\ta_first_author\t1
903 02/01/2042\ta second title\ta_second_barcode\ta_second_author\t2
904 EOF
905     is( $items_content, $expected_items_content, 'get_item_content should return correct items info without time (if dateonly => 1)' );
906 };
907
908 subtest 'Test limit parameter for SendQueuedMessages' => sub {
909     plan tests => 3;
910
911     my $dbh = C4::Context->dbh;
912
913     my $borrowernumber = Koha::Patron->new({
914         firstname    => 'Jane',
915         surname      => 'Smith',
916         categorycode => $patron_category,
917         branchcode   => $library->{branchcode},
918         dateofbirth  => $date,
919         smsalertnumber => undef,
920     })->store->borrowernumber;
921
922     $dbh->do(q|DELETE FROM message_queue|);
923     $my_message = {
924         'letter' => {
925             'content'      => 'a message',
926             'metadata'     => 'metadata',
927             'code'         => 'TEST_MESSAGE',
928             'content_type' => 'text/plain',
929             'title'        => 'message title'
930         },
931         'borrowernumber'         => $borrowernumber,
932         'to_address'             => undef,
933         'message_transport_type' => 'sms',
934         'from_address'           => 'from@example.com'
935     };
936     C4::Letters::EnqueueLetter($my_message);
937     C4::Letters::EnqueueLetter($my_message);
938     C4::Letters::EnqueueLetter($my_message);
939     C4::Letters::EnqueueLetter($my_message);
940     C4::Letters::EnqueueLetter($my_message);
941     my $messages_processed = C4::Letters::SendQueuedMessages( { limit => 1 } );
942     is( $messages_processed, 1,
943         'Processed 1 message with limit of 1 and 5 unprocessed messages' );
944     $messages_processed = C4::Letters::SendQueuedMessages( { limit => 2 } );
945     is( $messages_processed, 2,
946         'Processed 2 message with limit of 2 and 4 unprocessed messages' );
947     $messages_processed = C4::Letters::SendQueuedMessages( { limit => 3 } );
948     is( $messages_processed, 2,
949         'Processed 2 message with limit of 3 and 2 unprocessed messages' );
950 };
951
952 subtest 'Test message_id parameter for SendQueuedMessages' => sub {
953
954     plan tests => 7;
955
956     my $dbh = C4::Context->dbh;
957
958     my $borrowernumber = Koha::Patron->new({
959         firstname    => 'Jane',
960         surname      => 'Smith',
961         categorycode => $patron_category,
962         branchcode   => $library->{branchcode},
963         dateofbirth  => $date,
964         smsalertnumber => undef,
965     })->store->borrowernumber;
966
967     $dbh->do(q|DELETE FROM message_queue|);
968     $my_message = {
969         'letter' => {
970             'content'      => 'a message',
971             'metadata'     => 'metadata',
972             'code'         => 'TEST_MESSAGE',
973             'content_type' => 'text/plain',
974             'title'        => 'message title'
975         },
976         'borrowernumber'         => $borrowernumber,
977         'to_address'             => 'to@example.org',
978         'message_transport_type' => 'email',
979         'from_address'           => '@example.com' # invalid from_address
980     };
981     my $message_id = C4::Letters::EnqueueLetter($my_message);
982     my $processed = C4::Letters::SendQueuedMessages( { message_id => $message_id } );
983     is( $processed, 1, 'Processed 1 message when one message_id passed' );
984     my $message_1 = C4::Letters::GetMessage($message_id);
985     is( $message_1->{status}, 'failed', 'Invalid from_address => status failed' );
986     is( $message_1->{failure_code}, 'INVALID_EMAIL:from', 'Failure code set correctly for invalid email parameter');
987
988     $my_message->{from_address} = 'root@example.org'; # valid from_address
989     $message_id = C4::Letters::EnqueueLetter($my_message);
990     warning_like { C4::Letters::SendQueuedMessages( { message_id => $message_id } ); }
991         qr|Fake send_or_die|,
992         "SendQueuedMessages is using the mocked send_or_die routine";
993     $message_1 = C4::Letters::GetMessage($message_1->{message_id});
994     my $message_2 = C4::Letters::GetMessage($message_id);
995     is( $message_1->{status}, 'failed', 'Message 1 status is unchanged' );
996     is( $message_2->{status}, 'sent', 'Valid from_address => status sent' );
997 };