6ad1810706c9977f7b8e0c8617ae8cd2d8d7c713
[koha-equinox.git] / t / db_dependent / TestBuilder.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Copyright 2014 - Biblibre SARL
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
22 use Test::More tests => 13;
23 use Test::Warn;
24 use File::Basename qw(dirname);
25
26 use Koha::Database;
27 use Koha::Patrons;
28
29 BEGIN {
30     use_ok('t::lib::TestBuilder');
31 }
32
33 our $schema = Koha::Database->new->schema;
34 $schema->storage->txn_begin;
35 our $builder;
36
37 $schema->resultset('DefaultCircRule')->delete; # Is a singleton table
38
39 subtest 'Start with some trivial tests' => sub {
40     plan tests => 7;
41
42     $builder = t::lib::TestBuilder->new;
43     isnt( $builder, undef, 'We got a builder' );
44
45     my $data;
46     warning_like { $data = $builder->build; } qr/.+/, 'Catch a warning';
47     is( $data, undef, 'build without arguments returns undef' );
48     is( ref( $builder->schema ), 'Koha::Schema', 'check schema' );
49     is( ref( $builder->can('delete') ), 'CODE', 'found delete method' );
50
51     # invalid argument
52     warning_like { $builder->build({
53             source => 'Borrower',
54             value  => { surname => { invalid_hash => 1 } },
55         }) } qr/^Hash not allowed for surname/,
56         'Build should not accept a hash for this column';
57
58     # return undef if a record exists
59     my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
60     my $param = { source => 'Branch', value => { branchcode => $branchcode } };
61     warning_like { $builder->build( $param ) }
62         qr/Violation of unique constraint/,
63         'Catch warn on adding existing record';
64 };
65
66
67 subtest 'Build all sources' => sub {
68     plan tests => 1;
69
70     my @sources = $builder->schema->sources;
71     my @source_in_failure;
72     for my $source ( @sources ) {
73         my $res;
74         # Skip the source if it is a view
75         next if $schema->source($source)->isa('DBIx::Class::ResultSource::View');
76         eval { $res = $builder->build( { source => $source } ); };
77         push @source_in_failure, $source if $@ || !defined( $res );
78     }
79     is( @source_in_failure, 0,
80         'TestBuilder should be able to create an object for every source' );
81     if ( @source_in_failure ) {
82         diag( "The following sources have not been generated correctly: " .
83         join ', ', @source_in_failure );
84     }
85 };
86
87
88 subtest 'Test length of some generated fields' => sub {
89     plan tests => 3;
90
91     # Test the length of a returned character field
92     my $bookseller = $builder->build({ source  => 'Aqbookseller' });
93     my $max = $schema->source('Aqbookseller')->column_info('phone')->{size};
94     is( length( $bookseller->{phone} ) > 0, 1,
95         'The length for a generated string (phone) should not be zero' );
96     is( length( $bookseller->{phone} ) <= $max, 1,
97         'Check maximum length for a generated string (phone)' );
98
99     my $item = $builder->build({ source => 'Item' });
100     is( $item->{replacementprice}, sprintf("%.2f", $item->{replacementprice}), "The number of decimals for floats should not be more than 2" );
101 };
102
103
104 subtest 'Test FKs in overduerules_transport_type' => sub {
105     plan tests => 5;
106
107     my $my_overduerules_transport_type = {
108         message_transport_type => {
109             message_transport_type => 'my msg_t_t',
110         },
111         overduerules_id => {
112             branchcode   => 'codeB',
113             categorycode => 'codeC',
114         },
115     };
116
117     my $overduerules_transport_type = $builder->build({
118         source => 'OverduerulesTransportType',
119         value  => $my_overduerules_transport_type,
120     });
121     is(
122         $overduerules_transport_type->{message_transport_type},
123         $my_overduerules_transport_type->{message_transport_type}->{message_transport_type},
124         'build stores the message_transport_type correctly'
125     );
126     is(
127         $schema->resultset('Overduerule')->find( $overduerules_transport_type->{overduerules_id} )->branchcode,
128         $my_overduerules_transport_type->{overduerules_id}->{branchcode},
129         'build stores the branchcode correctly'
130     );
131     is(
132         $schema->resultset('Overduerule')->find( $overduerules_transport_type->{overduerules_id} )->categorycode,
133         $my_overduerules_transport_type->{overduerules_id}->{categorycode},
134         'build stores the categorycode correctly'
135     );
136     is(
137         $schema->resultset('MessageTransportType')->find( $overduerules_transport_type->{message_transport_type} )->message_transport_type,
138         $overduerules_transport_type->{message_transport_type},
139         'build stores the foreign key message_transport_type correctly'
140     );
141     isnt(
142         $schema->resultset('Overduerule')->find( $my_overduerules_transport_type->{overduerules_id} )->letter2,
143         undef,
144         'build generates values if they are not given'
145     );
146 };
147
148
149 subtest 'Tests with composite FK in userpermission' => sub {
150     plan tests => 9;
151
152     my $my_user_permission = default_userpermission();
153     my $user_permission = $builder->build({
154         source => 'UserPermission',
155         value  => $my_user_permission,
156     });
157
158     # Checks on top level of userpermission
159     isnt(
160         $user_permission->{borrowernumber},
161         undef,
162         'build generates a borrowernumber correctly'
163     );
164     is(
165         $user_permission->{code},
166         $my_user_permission->{code}->{code},
167         'build stores code correctly'
168     );
169
170     # Checks one level deeper userpermission -> borrower
171     my $patron = $schema->resultset('Borrower')->find({ borrowernumber => $user_permission->{borrowernumber} });
172     is(
173         $patron->surname,
174         $my_user_permission->{borrowernumber}->{surname},
175         'build stores surname correctly'
176     );
177     isnt(
178         $patron->cardnumber,
179         undef,
180         'build generated cardnumber'
181     );
182
183     # Checks two levels deeper userpermission -> borrower -> branch
184     my $branch = $schema->resultset('Branch')->find({ branchcode => $patron->branchcode->branchcode });
185     is(
186         $branch->branchname,
187         $my_user_permission->{borrowernumber}->{branchcode}->{branchname},
188         'build stores branchname correctly'
189     );
190     isnt(
191         $branch->branchaddress1,
192         undef,
193         'build generated branch address'
194     );
195
196     # Checks with composite FK: userpermission -> permission
197     my $perm = $schema->resultset('Permission')->find({ module_bit => $user_permission->{module_bit}, code => $my_user_permission->{code}->{code} });
198     isnt( $perm, undef, 'build generated record for composite FK' );
199     is(
200         $perm->code,
201         $my_user_permission->{code}->{code},
202         'build stored code correctly'
203     );
204     is(
205         $perm->description,
206         $my_user_permission->{code}->{description},
207         'build stored description correctly'
208     );
209 };
210
211 sub default_userpermission {
212     return {
213         borrowernumber => {
214             surname => 'my surname',
215             address => 'my adress',
216             city    => 'my city',
217             branchcode => {
218                 branchname => 'my branchname',
219             },
220             categorycode => {
221                 hidelostitems   => 0,
222                 category_type   => 'A',
223                 default_privacy => 'default',
224             },
225             privacy => 1,
226         },
227         module_bit => {
228             flag        => 'my flag',
229         },
230         code => {
231             code        => 'my code',
232             description => 'my desc',
233         },
234     };
235 }
236
237
238 subtest 'Test build with NULL values' => sub {
239     plan tests => 3;
240
241     # PK should not be null
242     my $params = { source => 'Branch', value => { branchcode => undef }};
243     warning_like { $builder->build( $params ) }
244         qr/Null value for branchcode/,
245         'Catch warn on adding branch with a null branchcode';
246     # Nullable column
247     my $info = $schema->source( 'Item' )->column_info( 'barcode' );
248     $params = { source => 'Item', value  => { barcode => undef }};
249     my $item = $builder->build( $params );
250     is( $info->{is_nullable} && $item && !defined( $item->{barcode} ), 1,
251         'Barcode can be NULL' );
252     # Nullable FK
253     $params = { source => 'Reserve', value  => { itemnumber => undef }};
254     my $reserve = $builder->build( $params );
255     $info = $schema->source( 'Reserve' )->column_info( 'itemnumber' );
256     is( $reserve && $info->{is_nullable} && $info->{is_foreign_key} &&
257         !defined( $reserve->{itemnumber} ), 1, 'Nullable FK' );
258 };
259
260
261 subtest 'Tests for delete method' => sub {
262     plan tests => 12;
263
264     # Test delete with single and multiple records
265     my $basket1 = $builder->build({ source => 'Aqbasket' });
266     my $basket2 = $builder->build({ source => 'Aqbasket' });
267     my $basket3 = $builder->build({ source => 'Aqbasket' });
268     my ( $id1, $id2 ) = ( $basket1->{basketno}, $basket2->{basketno} );
269     $builder->delete({ source => 'Aqbasket', records => $basket1 });
270     isnt( exists $basket1->{basketno}, 1, 'Delete cleared PK hash value' );
271
272     is( $builder->schema->resultset('Aqbasket')->search({ basketno => $id1 })->count, 0, 'Basket1 is no longer found' );
273     is( $builder->schema->resultset('Aqbasket')->search({ basketno => $id2 })->count, 1, 'Basket2 is still found' );
274     is( $builder->delete({ source => 'Aqbasket', records => [ $basket2, $basket3 ] }), 2, "Returned two delete attempts" );
275     is( $builder->schema->resultset('Aqbasket')->search({ basketno => $id2 })->count, 0, 'Basket2 is no longer found' );
276
277
278     # Test delete in table without primary key (..)
279     is( $schema->source('TmpHoldsqueue')->primary_columns, 0,
280         'Table without primary key detected' );
281     my $bibno = 123; # just a number
282     my $cnt1 = $schema->resultset('TmpHoldsqueue')->count;
283     # Insert a new record in TmpHoldsqueue with that biblionumber
284     my $val = { biblionumber => $bibno };
285     my $rec = $builder->build({ source => 'TmpHoldsqueue', value => $val });
286     my $cnt2 = $schema->resultset('TmpHoldsqueue')->count;
287     is( defined($rec) && $cnt2 == $cnt1 + 1 , 1, 'Created a record' );
288     is( $builder->delete({ source => 'TmpHoldsqueue', records => $rec }),
289         undef, 'delete returns undef' );
290     is( $rec->{biblionumber}, $bibno, 'Hash value untouched' );
291     is( $schema->resultset('TmpHoldsqueue')->count, $cnt2,
292         "Method did not delete record in table without PK" );
293
294     # Test delete with NULL values
295     $val = { branchcode => undef };
296     is( $builder->delete({ source => 'Branch', records => $val }), 0,
297         'delete returns zero for an undef search with one key' );
298     $val = { module_bit => 1, #catalogue
299              code       => undef };
300     is( $builder->delete({ source => 'Permission', records => $val }), 0,
301         'delete returns zero for an undef search with a composite PK' );
302 };
303
304
305 subtest 'Auto-increment values tests' => sub {
306     plan tests => 3;
307
308     # Pick a table with AI PK
309     my $source  = 'Biblio'; # table
310     my $column  = 'biblionumber'; # ai column
311
312     my $col_info = $schema->source( $source )->column_info( $column );
313     is( $col_info->{is_auto_increment}, 1, "biblio.biblionumber is detected as autoincrement");
314
315     # Create a biblio
316     my $biblio_1 = $builder->build({ source => $source });
317     # Get the AI value
318     my $ai_value = $biblio_1->{ biblionumber };
319     # Create a biblio
320     my $biblio_2 = $builder->build({ source => $source });
321     # Get the next AI value
322     my $next_ai_value = $biblio_2->{ biblionumber };
323     is( $ai_value + 1, $next_ai_value, "AI values are consecutive");
324
325     # respect autoincr column
326     warning_like { $builder->build({
327             source => $source,
328             value  => { biblionumber => 123 },
329         }) } qr/^Value not allowed for auto_incr/,
330         'Build should not overwrite an auto_incr column';
331 };
332
333 subtest 'Date handling' => sub {
334     plan tests => 2;
335
336     $builder = t::lib::TestBuilder->new;
337
338     my $patron = $builder->build( { source => 'Borrower' } );
339     is( length( $patron->{updated_on} ),  19, 'A timestamp column value should be YYYY-MM-DD HH:MM:SS' );
340     is( length( $patron->{dateofbirth} ), 10, 'A date column value should be YYYY-MM-DD' );
341 };
342
343 subtest 'Default values' => sub {
344     plan tests => 2;
345     $builder = t::lib::TestBuilder->new;
346     my $item = $builder->build( { source => 'Item' } );
347     is( $item->{more_subfields_xml}, undef, 'This xml field should be undef' );
348     $item = $builder->build( { source => 'Item', value => { more_subfields_xml => 'some xml' } } );
349     is( $item->{more_subfields_xml}, 'some xml', 'Default should not overwrite assigned value' );
350 };
351
352 subtest 'build_object() tests' => sub {
353
354     plan tests => 5;
355
356     $builder = t::lib::TestBuilder->new();
357
358     my $categorycode = $builder->build( { source => 'Category' } )->{categorycode};
359     my $itemtype = $builder->build( { source => 'Itemtype' } )->{itemtype};
360
361     my $issuing_rule = $builder->build_object(
362         {   class => 'Koha::IssuingRules',
363             value => {
364                 categorycode => $categorycode,
365                 itemtype     => $itemtype
366             }
367         }
368     );
369
370     is( ref($issuing_rule), 'Koha::IssuingRule', 'Type is correct' );
371     is( $issuing_rule->categorycode,
372         $categorycode, 'Category code correctly set' );
373     is( $issuing_rule->itemtype, $itemtype, 'Item type correctly set' );
374
375     subtest 'Test all classes' => sub {
376         my $Koha_modules_dir = dirname(__FILE__) . '/../../Koha';
377         my @koha_object_based_modules = `/bin/grep -rl -e '^sub object_class' $Koha_modules_dir`;
378         my @source_in_failure;
379         for my $module_filepath ( @koha_object_based_modules ) {
380             chomp $module_filepath;
381             next unless $module_filepath =~ m|\.pm$|;
382             my $module = $module_filepath;
383             $module =~ s|^.*/(Koha.*)\.pm$|$1|;
384             $module =~ s|/|::|g;
385             next if $module eq 'Koha::Objects';
386             eval "require $module";;
387             my $object = $builder->build_object( { class => $module } );
388             is( ref($object), $module->object_class, "Testing $module" );
389         }
390     };
391
392     subtest 'test parameters' => sub {
393         plan tests => 3;
394
395         warning_is { $issuing_rule = $builder->build_object( {} ); }
396         { carped => 'Missing class param' },
397             'The class parameter is mandatory, raises a warning if absent';
398         is( $issuing_rule, undef,
399             'If the class parameter is missing, undef is returned' );
400
401         warnings_like {
402             $builder->build_object(
403                 { class => 'Koha::Patrons', categorycode => 'foobar' } );
404         } qr{Unknown parameter\(s\): categorycode}, "Unknown parameter detected";
405     };
406 };
407
408 subtest '->build parameter' => sub {
409     plan tests => 4;
410
411     # Test to make sure build() warns user of unknown parameters.
412     warnings_are {
413         $builder->build({
414             source => 'Branch',
415             value => {
416                 branchcode => 'BRANCH_1'
417             }
418         })
419     } [], "No warnings on correct use";
420
421     warnings_like {
422         $builder->build({
423             source     => 'Branch',
424             branchcode => 'BRANCH_2' # This is wrong!
425         })
426     } qr/unknown param/i, "Carp unknown parameters";
427
428     warnings_like {
429         $builder->build({
430             zource     => 'Branch', # Intentional spelling error
431         })
432     } qr/Source parameter not specified/, "Catch warning on missing source";
433
434     warnings_like {
435         $builder->build(
436             { source => 'Borrower', categorycode => 'foobar' } );
437     } qr{Unknown parameter\(s\): categorycode}, "Unkown parameter detected";
438 };
439
440 $schema->storage->txn_rollback;