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