Bug 23151: (follow-up) tweaked tests
[koha.git] / t / db_dependent / Koha / Patron / Modifications.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use utf8;
21
22 use Test::More tests => 7;
23 use Test::Exception;
24
25 use t::lib::TestBuilder;
26 use t::lib::Mocks;
27
28 use Digest::MD5 qw( md5_base64 md5_hex );
29 use Try::Tiny;
30
31 use C4::Context;
32 use C4::Members;
33 use C4::Members::Attributes qw( GetBorrowerAttributes );
34 use Koha::Patrons;
35 use Koha::Patron::Attribute;
36
37 BEGIN {
38     use_ok('Koha::Patron::Modification');
39     use_ok('Koha::Patron::Modifications');
40 }
41
42 my $schema  = Koha::Database->new->schema;
43 my $builder = t::lib::TestBuilder->new;
44
45 subtest 'new() tests' => sub {
46
47     plan tests => 3;
48
49     $schema->storage->txn_begin;
50
51     Koha::Patron::Modifications->search->delete;
52
53     # Create new pending modification
54     Koha::Patron::Modification->new(
55         {   verification_token => '1234567890',
56             changed_fields     => 'surname,firstname',
57             surname            => 'Hall',
58             firstname          => 'Kyle'
59         }
60     )->store();
61
62     ## Get the new pending modification
63     my $borrower = Koha::Patron::Modifications->find(
64         { verification_token => '1234567890' } );
65
66     ## Verify we get the same data
67     is( $borrower->surname, 'Hall',
68         'Found modification has matching surname' );
69
70     throws_ok {
71         Koha::Patron::Modification->new(
72             {   verification_token => '1234567890',
73                 changed_fields     => 'surname,firstname',
74                 surname            => 'Hall',
75                 firstname          => 'Daria'
76             }
77         )->store();
78     }
79     'Koha::Exceptions::Patron::Modification::DuplicateVerificationToken',
80         'Attempting to add a duplicate verification raises the correct exception';
81     is( $@,
82         'Duplicate verification token 1234567890',
83         'Exception carries the right message'
84     );
85
86     $schema->storage->txn_rollback;
87 };
88
89 subtest 'store( extended_attributes ) tests' => sub {
90
91     plan tests => 4;
92
93     $schema->storage->txn_begin;
94
95     Koha::Patron::Modifications->search->delete;
96
97     my $patron
98         = $builder->build( { source => 'Borrower' } )->{borrowernumber};
99     my $verification_token = md5_hex( time().{}.rand().{}.$$ );
100     my $valid_json_text    = '[{"code":"CODE","value":"VALUE"}]';
101     my $invalid_json_text  = '[{"code":"CODE";"value":"VALUE"}]';
102
103     Koha::Patron::Modification->new(
104         {   verification_token  => $verification_token,
105             changed_fields      => 'borrowernumber,surname,extended_attributes',
106             borrowernumber      => $patron,
107             surname             => 'Hall',
108             extended_attributes => $valid_json_text
109         }
110     )->store();
111
112     my $patron_modification
113         = Koha::Patron::Modifications->search( { borrowernumber => $patron } )
114         ->next;
115
116     is( $patron_modification->surname,
117         'Hall', 'Patron modification correctly stored with valid JSON data' );
118     is( $patron_modification->extended_attributes,
119         $valid_json_text,
120         'Patron modification correctly stored with valid JSON data' );
121
122     $verification_token = md5_hex( time().{}.rand().{}.$$ );
123     throws_ok {
124         Koha::Patron::Modification->new(
125             {   verification_token  => $verification_token,
126                 changed_fields      => 'borrowernumber,surname,extended_attributes',
127                 borrowernumber      => $patron,
128                 surname             => 'Hall',
129                 extended_attributes => $invalid_json_text
130             }
131         )->store();
132     }
133     'Koha::Exceptions::Patron::Modification::InvalidData',
134         'Trying to store invalid JSON in extended_attributes field raises exception';
135
136     is( $@, 'The passed extended_attributes is not valid JSON' );
137
138     $schema->storage->txn_rollback;
139 };
140
141 subtest 'approve tests' => sub {
142
143     plan tests => 20;
144
145     $schema->storage->txn_begin;
146
147     Koha::Patron::Modifications->search->delete;
148
149     my $patron_hashref = $builder->build( { source => 'Borrower' } );
150     $builder->build(
151         { source => 'BorrowerAttributeType', value => { code => 'CODE_1' } }
152     );
153     $builder->build(
154         { source => 'BorrowerAttributeType', value => { code => 'CODE_2', repeatable => 1 } }
155     );
156     my $verification_token = md5_hex( time().{}.rand().{}.$$ );
157     my $valid_json_text
158         = '[{"code":"CODE_1","value":"VALUE_1"},{"code":"CODE_2","value":0}]';
159     my $patron_modification = Koha::Patron::Modification->new(
160         {   verification_token  => $verification_token,
161             changed_fields      => 'borrowernumber,firstname,extended_attributes',
162             borrowernumber      => $patron_hashref->{borrowernumber},
163             firstname           => 'Kyle',
164             extended_attributes => $valid_json_text
165         }
166     )->store();
167
168     ok( $patron_modification->approve,
169         'Patron modification correctly approved' );
170     my $patron = Koha::Patrons->find( $patron_hashref->{borrowernumber} );
171     isnt(
172         $patron->firstname,
173         $patron_hashref->{firstname},
174         'Patron modification changed firstname'
175     );
176     is( $patron->firstname, 'Kyle',
177         'Patron modification set the right firstname' );
178     my @patron_attributes = GetBorrowerAttributes( $patron->borrowernumber );
179     is( $patron_attributes[0][0]->{code},
180         'CODE_1', 'Patron modification correctly saved attribute code' );
181     is( $patron_attributes[0][0]->{value},
182         'VALUE_1', 'Patron modification correctly saved attribute value' );
183     is( $patron_attributes[0][1]->{code},
184         'CODE_2', 'Patron modification correctly saved attribute code' );
185     is( $patron_attributes[0][1]->{value},
186         0, 'Patron modification correctly saved attribute with value 0, not confused with delete' );
187
188     # Create a new Koha::Patron::Modification, skip extended_attributes to
189     # bypass checks
190     $patron_modification = Koha::Patron::Modification->new(
191         {   verification_token => $verification_token,
192             changed_fields     => 'borrowernumber,firstname',
193             borrowernumber     => $patron_hashref->{borrowernumber},
194             firstname          => 'Kylie'
195         }
196     )->store();
197
198     # Add invalid JSON to extended attributes
199     $patron_modification->extended_attributes(
200         '[{"code":"CODE";"values:VALUES"}]');
201     throws_ok { $patron_modification->approve }
202     'Koha::Exceptions::Patron::Modification::InvalidData',
203         'The right exception is thrown if invalid data is on extended_attributes';
204
205     $patron = Koha::Patrons->find( $patron_hashref->{borrowernumber} );
206     isnt( $patron->firstname, 'Kylie', 'Patron modification didn\'t apply' );
207
208     # Try changing only a portion of the attributes
209     my $bigger_json
210         = '[{"code":"CODE_2","value":"Tomasito"},{"code":"CODE_2","value":"None"}]';
211     $verification_token = md5_hex( time() . {} . rand() . {} . $$ );
212
213     $patron_modification = Koha::Patron::Modification->new(
214         {   verification_token  => $verification_token,
215             changed_fields      => 'borrowernumber,extended_attributes',
216             borrowernumber      => $patron->borrowernumber,
217             extended_attributes => $bigger_json
218         }
219     )->store();
220     ok( $patron_modification->approve,
221         'Patron modification correctly approved' );
222     @patron_attributes
223         = map { $_->unblessed }
224         Koha::Patron::Attributes->search(
225         { borrowernumber => $patron->borrowernumber } );
226
227     is( $patron_attributes[0]->{code},
228         'CODE_1', 'Untouched attribute type is preserved (code)' );
229     is( $patron_attributes[0]->{attribute},
230         'VALUE_1', 'Untouched attribute type is preserved (attribute)' );
231
232     is( $patron_attributes[1]->{code},
233         'CODE_2', 'Attribute updated correctly (code)' );
234     is( $patron_attributes[1]->{attribute},
235         'Tomasito', 'Attribute updated correctly (attribute)' );
236
237     is( $patron_attributes[2]->{code},
238         'CODE_2', 'Attribute updated correctly (code)' );
239     is( $patron_attributes[2]->{attribute},
240         'None', 'Attribute updated correctly (attribute)' );
241
242     my $empty_code_json = '[{"code":"CODE_2","value":""}]';
243     $verification_token = md5_hex( time() . {} . rand() . {} . $$ );
244
245     $patron_modification = Koha::Patron::Modification->new(
246         {   verification_token  => $verification_token,
247             changed_fields      => 'borrowernumber,extended_attributes',
248             borrowernumber      => $patron->borrowernumber,
249             extended_attributes => $empty_code_json
250         }
251     )->store();
252     ok( $patron_modification->approve,
253         'Patron modification correctly approved' );
254     @patron_attributes
255         = map { $_->unblessed }
256         Koha::Patron::Attributes->search(
257         { borrowernumber => $patron->borrowernumber } );
258
259     is( $patron_attributes[0]->{code},
260         'CODE_1', 'Untouched attribute type is preserved (code)' );
261     is( $patron_attributes[0]->{attribute},
262         'VALUE_1', 'Untouched attribute type is preserved (attribute)' );
263
264     my $count = Koha::Patron::Attributes->search({ borrowernumber => $patron->borrowernumber, code => 'CODE_2' })->count;
265     is( $count, 0, 'Attributes deleted when modification contained an empty one');
266
267     $schema->storage->txn_rollback;
268 };
269
270 subtest 'pending_count() and pending() tests' => sub {
271
272     plan tests => 16;
273
274     $schema->storage->txn_begin;
275
276     Koha::Patron::Modifications->search->delete;
277     my $library_1 = $builder->build( { source => 'Branch' } )->{branchcode};
278     my $library_2 = $builder->build( { source => 'Branch' } )->{branchcode};
279     $builder->build({ source => 'BorrowerAttributeType', value => { code => 'CODE_1' } });
280     $builder->build({ source => 'BorrowerAttributeType', value => { code => 'CODE_2', repeatable => 1 } });
281
282     my $patron_1
283         = $builder->build(
284         { source => 'Borrower', value => { branchcode => $library_1, flags => 1 } } );
285     my $patron_2
286         = $builder->build(
287         { source => 'Borrower', value => { branchcode => $library_2 } } );
288     my $patron_3
289         = $builder->build(
290         { source => 'Borrower', value => { branchcode => $library_2 } } );
291     $patron_1 = Koha::Patrons->find( $patron_1->{borrowernumber} );
292     $patron_2 = Koha::Patrons->find( $patron_2->{borrowernumber} );
293     $patron_3 = Koha::Patrons->find( $patron_3->{borrowernumber} );
294     my $verification_token_1 = md5_hex( time().{}.rand().{}.$$ );
295     my $verification_token_2 = md5_hex( time().{}.rand().{}.$$ );
296     my $verification_token_3 = md5_hex( time().{}.rand().{}.$$ );
297
298     Koha::Patron::Attribute->new({ borrowernumber => $patron_1->borrowernumber, code => 'CODE_1', attribute => 'hello' } )->store();
299     Koha::Patron::Attribute->new({ borrowernumber => $patron_2->borrowernumber, code => 'CODE_2', attribute => 'bye' } )->store();
300
301     my $modification_1 = Koha::Patron::Modification->new(
302         {   verification_token => $verification_token_1,
303             changed_fields     => 'borrowernumber,surname,firstname,extended_attributes',
304             borrowernumber     => $patron_1->borrowernumber,
305             surname            => 'Hall',
306             firstname          => 'Kyle',
307             extended_attributes => '[{"code":"CODE_1","value":""}]'
308         }
309     )->store();
310
311     is( Koha::Patron::Modifications->pending_count,
312         1, 'pending_count() correctly returns 1' );
313
314     my $modification_2 = Koha::Patron::Modification->new(
315         {   verification_token => $verification_token_2,
316             changed_fields     => 'borrowernumber,surname,firstname,extended_attributes',
317             borrowernumber     => $patron_2->borrowernumber,
318             surname            => 'Smith',
319             firstname          => 'Sandy',
320             extended_attributes => '[{"code":"CODE_2","value":"año"},{"code":"CODE_2","value":"ciao"}]'
321         }
322     )->store();
323
324     my $modification_3 = Koha::Patron::Modification->new(
325         {   verification_token => $verification_token_3,
326             changed_fields     => 'borrowernumber,surname,firstname',
327             borrowernumber     => $patron_3->borrowernumber,
328             surname            => 'Smithy',
329             firstname          => 'Sandy'
330         }
331     )->store();
332
333     is( Koha::Patron::Modifications->pending_count,
334         3, 'pending_count() correctly returns 3' );
335
336     my $pending = Koha::Patron::Modifications->pending();
337     is( scalar @{$pending}, 3, 'pending() returns an array with 3 elements' );
338
339     my @filtered_modifications = grep { $_->{borrowernumber} eq $patron_1->borrowernumber } @{$pending};
340     my $p1_pm = $filtered_modifications[0];
341     my $p1_pm_attribute_1 = $p1_pm->{extended_attributes}->[0];
342
343     is( scalar @{$p1_pm->{extended_attributes}}, 1, 'patron 1 has modification has one pending attribute modification' );
344     is( ref($p1_pm_attribute_1), 'Koha::Patron::Attribute', 'patron modification has single attribute object' );
345     is( $p1_pm_attribute_1->attribute, '', 'patron 1 has an empty value for the attribute' );
346
347     @filtered_modifications = grep { $_->{borrowernumber} eq $patron_2->borrowernumber } @{$pending};
348     my $p2_pm = $filtered_modifications[0];
349
350     is( scalar @{$p2_pm->{extended_attributes}}, 2 , 'patron 2 has 2 attribute modifications' );
351
352     my $p2_pm_attribute_1 = $p2_pm->{extended_attributes}->[0];
353     my $p2_pm_attribute_2 = $p2_pm->{extended_attributes}->[1];
354
355     is( ref($p2_pm_attribute_1), 'Koha::Patron::Attribute', 'patron modification has single attribute object' );
356     is( ref($p2_pm_attribute_2), 'Koha::Patron::Attribute', 'patron modification has single attribute object' );
357
358     is( $p2_pm_attribute_1->attribute, 'año', 'patron modification has the right attribute change' );
359     is( $p2_pm_attribute_2->attribute, 'ciao', 'patron modification has the right attribute change' );
360
361
362     t::lib::Mocks::mock_userenv({ patron => $patron_1 });
363     is( Koha::Patron::Modifications->pending_count($library_1),
364         1, 'pending_count() correctly returns 1 if filtered by library' );
365
366     is( Koha::Patron::Modifications->pending_count($library_2),
367         2, 'pending_count() correctly returns 2 if filtered by library' );
368
369     $modification_1->approve;
370
371     is( Koha::Patron::Modifications->pending_count,
372         2, 'pending_count() correctly returns 2' );
373
374     $modification_2->approve;
375
376     is( Koha::Patron::Modifications->pending_count,
377         1, 'pending_count() correctly returns 1' );
378
379     $modification_3->approve;
380
381     is( Koha::Patron::Modifications->pending_count,
382         0, 'pending_count() correctly returns 0' );
383
384     $schema->storage->txn_rollback;
385 };
386
387 subtest 'dateofbirth tests' => sub {
388     plan tests => 7;
389
390     $schema->storage->txn_begin;
391
392     # Cleaning the field
393     my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { dateofbirth => '1980-01-01', surname => 'a_surname' } } );
394     my $patron_modification = Koha::Patron::Modification->new( {
395             changed_fields => 'borrowernumber,dateofbirth',
396             borrowernumber => $patron->borrowernumber,
397             dateofbirth => undef
398     })->store;
399     $patron_modification->approve;
400
401     $patron->discard_changes;
402     is( $patron->dateofbirth, undef, 'dateofbirth must a been set to NULL if required' );
403
404     # FIXME ->approve must have been removed it, but it did not. There may be an hidden bug here.
405     Koha::Patron::Modifications->search({ borrowernumber => $patron->borrowernumber })->delete;
406
407     # Adding a dateofbirth
408     $patron_modification = Koha::Patron::Modification->new( {
409             changed_fields => 'borrowernumber,dateofbirth',
410             borrowernumber => $patron->borrowernumber,
411             dateofbirth => '1980-02-02'
412     })->store;
413     $patron_modification->approve;
414
415     $patron->discard_changes;
416     is( $patron->dateofbirth, '1980-02-02', 'dateofbirth must a been set' );
417     is( $patron->surname, 'a_surname', 'surname must not be updated' );
418
419     # FIXME ->approve must have been removed it, but it did not. There may be an hidden bug here.
420     Koha::Patron::Modifications->search({ borrowernumber => $patron->borrowernumber })->delete;
421
422     # Modifying a dateofbirth
423     $patron_modification = Koha::Patron::Modification->new( {
424             changed_fields => 'borrowernumber,dateofbirth',
425             borrowernumber => $patron->borrowernumber,
426             dateofbirth => '1980-03-03',
427             surname => undef
428     })->store;
429     $patron_modification->approve;
430
431     $patron->discard_changes;
432     is( $patron->dateofbirth, '1980-03-03', 'dateofbirth must a been updated' );
433     is( $patron->surname, 'a_surname', 'surname must not be updated' );
434
435     # FIXME ->approve must have been removed it, but it did not. There may be an hidden bug here.
436     Koha::Patron::Modifications->search({ borrowernumber => $patron->borrowernumber })->delete;
437
438     # Modifying something else
439     $patron_modification = Koha::Patron::Modification->new( {
440             changed_fields => 'borrowernumber,surname',
441             borrowernumber => $patron->borrowernumber,
442             surname => 'another_surname',
443             dateofbirth => undef
444     })->store;
445     $patron_modification->approve;
446
447     $patron->discard_changes;
448     is( $patron->surname, 'another_surname', 'surname must be updated' );
449     is( $patron->dateofbirth, '1980-03-03', 'dateofbirth should not have been updated if not needed' );
450
451     $schema->storage->txn_rollback;
452 };