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