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