Bug 17828: (QA followup) Add ->type and reuse it
[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 Test::More tests => 6;
21 use Test::Exception;
22
23 use t::lib::TestBuilder;
24
25 use Digest::MD5 qw( md5_base64 md5_hex );
26 use Try::Tiny;
27
28 use C4::Context;
29 use C4::Members;
30 use C4::Members::Attributes qw( GetBorrowerAttributes );
31 use Koha::Patrons;
32 use Koha::Patron::Attribute;
33
34 BEGIN {
35     use_ok('Koha::Patron::Modification');
36     use_ok('Koha::Patron::Modifications');
37 }
38
39 my $schema  = Koha::Database->new->schema;
40 my $builder = t::lib::TestBuilder->new;
41
42 subtest 'new() tests' => sub {
43
44     plan tests => 3;
45
46     $schema->storage->txn_begin;
47
48     Koha::Patron::Modifications->search->delete;
49
50     # Create new pending modification
51     Koha::Patron::Modification->new(
52         {   verification_token => '1234567890',
53             surname            => 'Hall',
54             firstname          => 'Kyle'
55         }
56     )->store();
57
58     ## Get the new pending modification
59     my $borrower = Koha::Patron::Modifications->find(
60         { verification_token => '1234567890' } );
61
62     ## Verify we get the same data
63     is( $borrower->surname, 'Hall',
64         'Found modification has matching surname' );
65
66     throws_ok {
67         Koha::Patron::Modification->new(
68             {   verification_token => '1234567890',
69                 surname            => 'Hall',
70                 firstname          => 'Daria'
71             }
72         )->store();
73     }
74     'Koha::Exceptions::Patron::Modification::DuplicateVerificationToken',
75         'Attempting to add a duplicate verification raises the correct exception';
76     is( $@,
77         'Duplicate verification token 1234567890',
78         'Exception carries the right message'
79     );
80
81     $schema->storage->txn_rollback;
82 };
83
84 subtest 'store( extended_attributes ) tests' => sub {
85
86     plan tests => 4;
87
88     $schema->storage->txn_begin;
89
90     Koha::Patron::Modifications->search->delete;
91
92     my $patron
93         = $builder->build( { source => 'Borrower' } )->{borrowernumber};
94     my $verification_token = md5_hex( time().{}.rand().{}.$$ );
95     my $valid_json_text    = '[{"code":"CODE","value":"VALUE"}]';
96     my $invalid_json_text  = '[{"code":"CODE";"value":"VALUE"}]';
97
98     Koha::Patron::Modification->new(
99         {   verification_token  => $verification_token,
100             borrowernumber      => $patron,
101             surname             => 'Hall',
102             extended_attributes => $valid_json_text
103         }
104     )->store();
105
106     my $patron_modification
107         = Koha::Patron::Modifications->search( { borrowernumber => $patron } )
108         ->next;
109
110     is( $patron_modification->surname,
111         'Hall', 'Patron modification correctly stored with valid JSON data' );
112     is( $patron_modification->extended_attributes,
113         $valid_json_text,
114         'Patron modification correctly stored with valid JSON data' );
115
116     $verification_token = md5_hex( time().{}.rand().{}.$$ );
117     throws_ok {
118         Koha::Patron::Modification->new(
119             {   verification_token  => $verification_token,
120                 borrowernumber      => $patron,
121                 surname             => 'Hall',
122                 extended_attributes => $invalid_json_text
123             }
124         )->store();
125     }
126     'Koha::Exceptions::Patron::Modification::InvalidData',
127         'Trying to store invalid JSON in extended_attributes field raises exception';
128
129     is( $@, 'The passed extended_attributes is not valid JSON' );
130
131     $schema->storage->txn_rollback;
132 };
133
134 subtest 'approve tests' => sub {
135
136     plan tests => 20;
137
138     $schema->storage->txn_begin;
139
140     Koha::Patron::Modifications->search->delete;
141
142     my $patron_hashref = $builder->build( { source => 'Borrower' } );
143     $builder->build(
144         { source => 'BorrowerAttributeType', value => { code => 'CODE_1' } }
145     );
146     $builder->build(
147         { source => 'BorrowerAttributeType', value => { code => 'CODE_2' } }
148     );
149     my $verification_token = md5_hex( time().{}.rand().{}.$$ );
150     my $valid_json_text
151         = '[{"code":"CODE_1","value":"VALUE_1"},{"code":"CODE_2","value":0}]';
152     my $patron_modification = Koha::Patron::Modification->new(
153         {   borrowernumber      => $patron_hashref->{borrowernumber},
154             firstname           => 'Kyle',
155             verification_token  => $verification_token,
156             extended_attributes => $valid_json_text
157         }
158     )->store();
159
160     ok( $patron_modification->approve,
161         'Patron modification correctly approved' );
162     my $patron = Koha::Patrons->find( $patron_hashref->{borrowernumber} );
163     isnt(
164         $patron->firstname,
165         $patron_hashref->{firstname},
166         'Patron modification changed firstname'
167     );
168     is( $patron->firstname, 'Kyle',
169         'Patron modification set the right firstname' );
170     my @patron_attributes = GetBorrowerAttributes( $patron->borrowernumber );
171     is( $patron_attributes[0][0]->{code},
172         'CODE_1', 'Patron modification correctly saved attribute code' );
173     is( $patron_attributes[0][0]->{value},
174         'VALUE_1', 'Patron modification correctly saved attribute value' );
175     is( $patron_attributes[0][1]->{code},
176         'CODE_2', 'Patron modification correctly saved attribute code' );
177     is( $patron_attributes[0][1]->{value},
178         0, 'Patron modification correctly saved attribute with value 0, not confused with delete' );
179
180     # Create a new Koha::Patron::Modification, skip extended_attributes to
181     # bypass checks
182     $patron_modification = Koha::Patron::Modification->new(
183         {   borrowernumber     => $patron_hashref->{borrowernumber},
184             firstname          => 'Kylie',
185             verification_token => $verification_token
186         }
187     )->store();
188
189     # Add invalid JSON to extended attributes
190     $patron_modification->extended_attributes(
191         '[{"code":"CODE";"values:VALUES"}]');
192     throws_ok { $patron_modification->approve }
193     'Koha::Exceptions::Patron::Modification::InvalidData',
194         'The right exception is thrown if invalid data is on extended_attributes';
195
196     $patron = Koha::Patrons->find( $patron_hashref->{borrowernumber} );
197     isnt( $patron->firstname, 'Kylie', 'Patron modification didn\'t apply' );
198
199     # Try changing only a portion of the attributes
200     my $bigger_json
201         = '[{"code":"CODE_2","value":"Tomasito"},{"code":"CODE_2","value":"None"}]';
202     $verification_token = md5_hex( time() . {} . rand() . {} . $$ );
203
204     $patron_modification = Koha::Patron::Modification->new(
205         {   borrowernumber      => $patron->borrowernumber,
206             extended_attributes => $bigger_json,
207             verification_token  => $verification_token
208         }
209     )->store();
210     ok( $patron_modification->approve,
211         'Patron modification correctly approved' );
212     @patron_attributes
213         = map { $_->unblessed }
214         Koha::Patron::Attributes->search(
215         { borrowernumber => $patron->borrowernumber } );
216
217     is( $patron_attributes[0]->{code},
218         'CODE_1', 'Untouched attribute type is preserved (code)' );
219     is( $patron_attributes[0]->{attribute},
220         'VALUE_1', 'Untouched attribute type is preserved (attribute)' );
221
222     is( $patron_attributes[1]->{code},
223         'CODE_2', 'Attribute updated correctly (code)' );
224     is( $patron_attributes[1]->{attribute},
225         'Tomasito', 'Attribute updated correctly (attribute)' );
226
227     is( $patron_attributes[2]->{code},
228         'CODE_2', 'Attribute updated correctly (code)' );
229     is( $patron_attributes[2]->{attribute},
230         'None', 'Attribute updated correctly (attribute)' );
231
232     my $empty_code_json = '[{"code":"CODE_2","value":""}]';
233     $verification_token = md5_hex( time() . {} . rand() . {} . $$ );
234
235     $patron_modification = Koha::Patron::Modification->new(
236         {   borrowernumber      => $patron->borrowernumber,
237             extended_attributes => $empty_code_json,
238             verification_token  => $verification_token
239         }
240     )->store();
241     ok( $patron_modification->approve,
242         'Patron modification correctly approved' );
243     @patron_attributes
244         = map { $_->unblessed }
245         Koha::Patron::Attributes->search(
246         { borrowernumber => $patron->borrowernumber } );
247
248     is( $patron_attributes[0]->{code},
249         'CODE_1', 'Untouched attribute type is preserved (code)' );
250     is( $patron_attributes[0]->{attribute},
251         'VALUE_1', 'Untouched attribute type is preserved (attribute)' );
252
253     my $count = Koha::Patron::Attributes->search({ borrowernumber => $patron->borrowernumber, code => 'CODE_2' })->count;
254     is( $count, 0, 'Attributes deleted when modification contained an empty one');
255
256     $schema->storage->txn_rollback;
257 };
258
259 subtest 'pending_count() and pending() tests' => sub {
260
261     plan tests => 16;
262
263     $schema->storage->txn_begin;
264
265     Koha::Patron::Modifications->search->delete;
266     my $library_1 = $builder->build( { source => 'Branch' } )->{branchcode};
267     my $library_2 = $builder->build( { source => 'Branch' } )->{branchcode};
268     $builder->build({ source => 'BorrowerAttributeType', value => { code => 'CODE_1' } });
269     $builder->build({ source => 'BorrowerAttributeType', value => { code => 'CODE_2', repeatable => 1 } });
270
271     my $patron_1
272         = $builder->build(
273         { source => 'Borrower', value => { branchcode => $library_1 } } )
274         ->{borrowernumber};
275     my $patron_2
276         = $builder->build(
277         { source => 'Borrower', value => { branchcode => $library_2 } } )
278         ->{borrowernumber};
279     my $patron_3
280         = $builder->build(
281         { source => 'Borrower', value => { branchcode => $library_2 } } )
282         ->{borrowernumber};
283     my $verification_token_1 = md5_hex( time().{}.rand().{}.$$ );
284     my $verification_token_2 = md5_hex( time().{}.rand().{}.$$ );
285     my $verification_token_3 = md5_hex( time().{}.rand().{}.$$ );
286
287     Koha::Patron::Attribute->new({ borrowernumber => $patron_1, code => 'CODE_1', attribute => 'hello' } )->store();
288     Koha::Patron::Attribute->new({ borrowernumber => $patron_2, code => 'CODE_2', attribute => 'bye' } )->store();
289
290     my $modification_1 = Koha::Patron::Modification->new(
291         {   borrowernumber     => $patron_1,
292             surname            => 'Hall',
293             firstname          => 'Kyle',
294             verification_token => $verification_token_1,
295             extended_attributes => '[{"code":"CODE_1","value":""}]'
296         }
297     )->store();
298
299     is( Koha::Patron::Modifications->pending_count,
300         1, 'pending_count() correctly returns 1' );
301
302     my $modification_2 = Koha::Patron::Modification->new(
303         {   borrowernumber     => $patron_2,
304             surname            => 'Smith',
305             firstname          => 'Sandy',
306             verification_token => $verification_token_2,
307             extended_attributes => '[{"code":"CODE_2","value":"chau"},{"code":"CODE_2","value":"ciao"}]'
308         }
309     )->store();
310
311     my $modification_3 = Koha::Patron::Modification->new(
312         {   borrowernumber     => $patron_3,
313             surname            => 'Smithy',
314             firstname          => 'Sandy',
315             verification_token => $verification_token_3
316         }
317     )->store();
318
319     is( Koha::Patron::Modifications->pending_count,
320         3, 'pending_count() correctly returns 3' );
321
322     my $pending = Koha::Patron::Modifications->pending();
323     is( scalar @{$pending}, 3, 'pending() returns an array with 3 elements' );
324
325     my @filtered_modifications = grep { $_->{borrowernumber} eq $patron_1 } @{$pending};
326     my $p1_pm = $filtered_modifications[0];
327     my $p1_pm_attribute_1 = $p1_pm->{extended_attributes}->[0];
328
329     is( scalar @{$p1_pm->{extended_attributes}}, 1, 'patron 1 has modification has one pending attribute modification' );
330     is( ref($p1_pm_attribute_1), 'Koha::Patron::Attribute', 'patron modification has single attribute object' );
331     is( $p1_pm_attribute_1->attribute, '', 'patron 1 has an empty value for the attribute' );
332
333     @filtered_modifications = grep { $_->{borrowernumber} eq $patron_2 } @{$pending};
334     my $p2_pm = $filtered_modifications[0];
335
336     is( scalar @{$p2_pm->{extended_attributes}}, 2 , 'patron 2 has 2 attribute modifications' );
337
338     my $p2_pm_attribute_1 = $p2_pm->{extended_attributes}->[0];
339     my $p2_pm_attribute_2 = $p2_pm->{extended_attributes}->[1];
340
341     is( ref($p2_pm_attribute_1), 'Koha::Patron::Attribute', 'patron modification has single attribute object' );
342     is( ref($p2_pm_attribute_2), 'Koha::Patron::Attribute', 'patron modification has single attribute object' );
343
344     is( $p2_pm_attribute_1->attribute, 'chau', 'patron modification has the right attribute change' );
345     is( $p2_pm_attribute_2->attribute, 'ciao', 'patron modification has the right attribute change' );
346
347     is( Koha::Patron::Modifications->pending_count($library_1),
348         1, 'pending_count() correctly returns 1 if filtered by library' );
349
350     is( Koha::Patron::Modifications->pending_count($library_2),
351         2, 'pending_count() correctly returns 2 if filtered by library' );
352
353     $modification_1->approve;
354
355     is( Koha::Patron::Modifications->pending_count,
356         2, 'pending_count() correctly returns 2' );
357
358     $modification_2->approve;
359
360     is( Koha::Patron::Modifications->pending_count,
361         1, 'pending_count() correctly returns 1' );
362
363     $modification_3->approve;
364
365     is( Koha::Patron::Modifications->pending_count,
366         0, 'pending_count() correctly returns 0' );
367
368     $schema->storage->txn_rollback;
369 };
370
371 1;