Bug 17835: (followup) Make TestBuilder skip views
[koha-ffzg.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 => 11;
23 use Test::Warn;
24 use Data::Dumper qw(Dumper);
25
26 use Koha::Database;
27
28 BEGIN {
29     use_ok('t::lib::TestBuilder');
30 }
31
32 my $schema = Koha::Database->new->schema;
33 $schema->storage->txn_begin;
34 my $builder;
35
36
37 subtest 'Start with some trivial tests' => sub {
38     plan tests => 6;
39
40     $builder = t::lib::TestBuilder->new;
41     isnt( $builder, undef, 'We got a builder' );
42
43     is( $builder->build, undef, 'build without arguments returns undef' );
44     is( ref( $builder->schema ), 'Koha::Schema', 'check schema' );
45     is( ref( $builder->can('delete') ), 'CODE', 'found delete method' );
46
47     # invalid argument
48     warning_like { $builder->build({
49             source => 'Borrower',
50             value  => { surname => { invalid_hash => 1 } },
51         }) } qr/^Hash not allowed for surname/,
52         'Build should not accept a hash for this column';
53
54     # return undef if a record exists
55     my $branchcode = $builder->build({ source => 'Branch' })->{branchcode};
56     my $param = { source => 'Branch', value => { branchcode => $branchcode } };
57     warning_like { $builder->build( $param ) }
58         qr/Violation of unique constraint/,
59         'Catch warn on adding existing record';
60 };
61
62
63 subtest 'Build all sources' => sub {
64     plan tests => 1;
65
66     my @sources = $builder->schema->sources;
67     my @source_in_failure;
68     for my $source ( @sources ) {
69         my $res;
70         # Skip the source if it is a view
71         next if $schema->source($source)->isa('DBIx::Class::ResultSource::View');
72         eval { $res = $builder->build( { source => $source } ); };
73         push @source_in_failure, $source if $@ || !defined( $res );
74     }
75     is( @source_in_failure, 0,
76         'TestBuilder should be able to create an object for every source' );
77     if ( @source_in_failure ) {
78         diag( "The following sources have not been generated correctly: " .
79         join ', ', @source_in_failure );
80     }
81 };
82
83
84 subtest 'Test length of some generated fields' => sub {
85     plan tests => 2;
86
87     # Test the length of a returned character field
88     my $bookseller = $builder->build({ source  => 'Aqbookseller' });
89     my $max = $schema->source('Aqbookseller')->column_info('phone')->{size};
90     is( length( $bookseller->{phone} ) > 0, 1,
91         'The length for a generated string (phone) should not be zero' );
92     is( length( $bookseller->{phone} ) <= $max, 1,
93         'Check maximum length for a generated string (phone)' );
94 };
95
96
97 subtest 'Test FKs in overduerules_transport_type' => sub {
98     plan tests => 5;
99
100     my $my_overduerules_transport_type = {
101         message_transport_type => {
102             message_transport_type => 'my msg_t_t',
103         },
104         overduerules_id => {
105             branchcode   => 'codeB',
106             categorycode => 'codeC',
107         },
108     };
109
110     my $overduerules_transport_type = $builder->build({
111         source => 'OverduerulesTransportType',
112         value  => $my_overduerules_transport_type,
113     });
114     is(
115         $overduerules_transport_type->{message_transport_type},
116         $my_overduerules_transport_type->{message_transport_type}->{message_transport_type},
117         'build stores the message_transport_type correctly'
118     );
119     is(
120         $schema->resultset('Overduerule')->find( $overduerules_transport_type->{overduerules_id} )->branchcode,
121         $my_overduerules_transport_type->{overduerules_id}->{branchcode},
122         'build stores the branchcode correctly'
123     );
124     is(
125         $schema->resultset('Overduerule')->find( $overduerules_transport_type->{overduerules_id} )->categorycode,
126         $my_overduerules_transport_type->{overduerules_id}->{categorycode},
127         'build stores the categorycode correctly'
128     );
129     is(
130         $schema->resultset('MessageTransportType')->find( $overduerules_transport_type->{message_transport_type} )->message_transport_type,
131         $overduerules_transport_type->{message_transport_type},
132         'build stores the foreign key message_transport_type correctly'
133     );
134     isnt(
135         $schema->resultset('Overduerule')->find( $my_overduerules_transport_type->{overduerules_id} )->letter2,
136         undef,
137         'build generates values if they are not given'
138     );
139 };
140
141
142 subtest 'Tests with composite FK in userpermission' => sub {
143     plan tests => 9;
144
145     my $my_user_permission = default_userpermission();
146     my $user_permission = $builder->build({
147         source => 'UserPermission',
148         value  => $my_user_permission,
149     });
150
151     # Checks on top level of userpermission
152     isnt(
153         $user_permission->{borrowernumber},
154         undef,
155         'build generates a borrowernumber correctly'
156     );
157     is(
158         $user_permission->{code},
159         $my_user_permission->{code}->{code},
160         'build stores code correctly'
161     );
162
163     # Checks one level deeper userpermission -> borrower
164     my $patron = $schema->resultset('Borrower')->find({ borrowernumber => $user_permission->{borrowernumber} });
165     is(
166         $patron->surname,
167         $my_user_permission->{borrowernumber}->{surname},
168         'build stores surname correctly'
169     );
170     isnt(
171         $patron->cardnumber,
172         undef,
173         'build generated cardnumber'
174     );
175
176     # Checks two levels deeper userpermission -> borrower -> branch
177     my $branch = $schema->resultset('Branch')->find({ branchcode => $patron->branchcode->branchcode });
178     is(
179         $branch->branchname,
180         $my_user_permission->{borrowernumber}->{branchcode}->{branchname},
181         'build stores branchname correctly'
182     );
183     isnt(
184         $branch->branchaddress1,
185         undef,
186         'build generated branch address'
187     );
188
189     # Checks with composite FK: userpermission -> permission
190     my $perm = $schema->resultset('Permission')->find({ module_bit => $user_permission->{module_bit}, code => $my_user_permission->{code}->{code} });
191     isnt( $perm, undef, 'build generated record for composite FK' );
192     is(
193         $perm->code,
194         $my_user_permission->{code}->{code},
195         'build stored code correctly'
196     );
197     is(
198         $perm->description,
199         $my_user_permission->{code}->{description},
200         'build stored description correctly'
201     );
202 };
203
204 sub default_userpermission {
205     return {
206         borrowernumber => {
207             surname => 'my surname',
208             address => 'my adress',
209             city    => 'my city',
210             branchcode => {
211                 branchname => 'my branchname',
212             },
213             categorycode => {
214                 hidelostitems   => 0,
215                 category_type   => 'A',
216                 default_privacy => 'default',
217             },
218             privacy => 1,
219         },
220         module_bit => {
221             flag        => 'my flag',
222         },
223         code => {
224             code        => 'my code',
225             description => 'my desc',
226         },
227     };
228 }
229
230
231 subtest 'Test build with NULL values' => sub {
232     plan tests => 3;
233
234     # PK should not be null
235     my $params = { source => 'Branch', value => { branchcode => undef }};
236     warning_like { $builder->build( $params ) }
237         qr/Null value for branchcode/,
238         'Catch warn on adding branch with a null branchcode';
239     # Nullable column
240     my $info = $schema->source( 'Item' )->column_info( 'barcode' );
241     $params = { source => 'Item', value  => { barcode => undef }};
242     my $item = $builder->build( $params );
243     is( $info->{is_nullable} && $item && !defined( $item->{barcode} ), 1,
244         'Barcode can be NULL' );
245     # Nullable FK
246     $params = { source => 'Reserve', value  => { itemnumber => undef }};
247     my $reserve = $builder->build( $params );
248     $info = $schema->source( 'Reserve' )->column_info( 'itemnumber' );
249     is( $reserve && $info->{is_nullable} && $info->{is_foreign_key} &&
250         !defined( $reserve->{itemnumber} ), 1, 'Nullable FK' );
251 };
252
253
254 subtest 'Tests for delete method' => sub {
255     plan tests => 12;
256
257     # Test delete with single and multiple records
258     my $basket1 = $builder->build({ source => 'Aqbasket' });
259     my $basket2 = $builder->build({ source => 'Aqbasket' });
260     my $basket3 = $builder->build({ source => 'Aqbasket' });
261     my ( $id1, $id2 ) = ( $basket1->{basketno}, $basket2->{basketno} );
262     $builder->delete({ source => 'Aqbasket', records => $basket1 });
263     isnt( exists $basket1->{basketno}, 1, 'Delete cleared PK hash value' );
264
265     is( $builder->schema->resultset('Aqbasket')->search({ basketno => $id1 })->count, 0, 'Basket1 is no longer found' );
266     is( $builder->schema->resultset('Aqbasket')->search({ basketno => $id2 })->count, 1, 'Basket2 is still found' );
267     is( $builder->delete({ source => 'Aqbasket', records => [ $basket2, $basket3 ] }), 2, "Returned two delete attempts" );
268     is( $builder->schema->resultset('Aqbasket')->search({ basketno => $id2 })->count, 0, 'Basket2 is no longer found' );
269
270
271     # Test delete in table without primary key (..)
272     is( $schema->source('TmpHoldsqueue')->primary_columns, 0,
273         'Table without primary key detected' );
274     my $bibno = 123; # just a number
275     my $cnt1 = $schema->resultset('TmpHoldsqueue')->count;
276     # Insert a new record in TmpHoldsqueue with that biblionumber
277     my $val = { biblionumber => $bibno };
278     my $rec = $builder->build({ source => 'TmpHoldsqueue', value => $val });
279     my $cnt2 = $schema->resultset('TmpHoldsqueue')->count;
280     is( defined($rec) && $cnt2 == $cnt1 + 1 , 1, 'Created a record' );
281     is( $builder->delete({ source => 'TmpHoldsqueue', records => $rec }),
282         undef, 'delete returns undef' );
283     is( $rec->{biblionumber}, $bibno, 'Hash value untouched' );
284     is( $schema->resultset('TmpHoldsqueue')->count, $cnt2,
285         "Method did not delete record in table without PK" );
286
287     # Test delete with NULL values
288     $val = { branchcode => undef };
289     is( $builder->delete({ source => 'Branch', records => $val }), 0,
290         'delete returns zero for an undef search with one key' );
291     $val = { module_bit => 1, #catalogue
292              code       => undef };
293     is( $builder->delete({ source => 'Permission', records => $val }), 0,
294         'delete returns zero for an undef search with a composite PK' );
295 };
296
297
298 subtest 'Auto-increment values tests' => sub {
299     plan tests => 3;
300
301     # Pick a table with AI PK
302     my $source  = 'Biblio'; # table
303     my $column  = 'biblionumber'; # ai column
304
305     my $col_info = $schema->source( $source )->column_info( $column );
306     is( $col_info->{is_auto_increment}, 1, "biblio.biblionumber is detected as autoincrement");
307
308     # Create a biblio
309     my $biblio_1 = $builder->build({ source => $source });
310     # Get the AI value
311     my $ai_value = $biblio_1->{ biblionumber };
312     # Create a biblio
313     my $biblio_2 = $builder->build({ source => $source });
314     # Get the next AI value
315     my $next_ai_value = $biblio_2->{ biblionumber };
316     is( $ai_value + 1, $next_ai_value, "AI values are consecutive");
317
318     # respect autoincr column
319     warning_like { $builder->build({
320             source => $source,
321             value  => { biblionumber => 123 },
322         }) } qr/^Value not allowed for auto_incr/,
323         'Build should not overwrite an auto_incr column';
324 };
325
326 subtest 'Date handling' => sub {
327     plan tests => 2;
328
329     $builder = t::lib::TestBuilder->new;
330
331     my $patron = $builder->build( { source => 'Borrower' } );
332     is( length( $patron->{updated_on} ),  19, 'A timestamp column value should be YYYY-MM-DD HH:MM:SS' );
333     is( length( $patron->{dateofbirth} ), 10, 'A date column value should be YYYY-MM-DD' );
334
335 };
336
337 subtest 'Default values' => sub {
338     plan tests => 2;
339     $builder = t::lib::TestBuilder->new;
340     my $item = $builder->build( { source => 'Item' } );
341     is( $item->{more_subfields_xml}, undef, 'This xml field should be undef' );
342     $item = $builder->build( { source => 'Item', value => { more_subfields_xml => 'some xml' } } );
343     is( $item->{more_subfields_xml}, 'some xml', 'Default should not overwrite assigned value' );
344 };
345
346 $schema->storage->txn_rollback;
347
348 1;