Bug 15049: (followup) Add warning about "No active currency"
[koha.git] / t / lib / TestBuilder.pm
blob254af61ab75562e5b765b781cd16859864e1cc30
1 package t::lib::TestBuilder;
3 use Modern::Perl;
4 use Koha::Database;
5 use String::Random;
8 my $gen_type = {
9 tinyint => \&_gen_int,
10 smallint => \&_gen_int,
11 mediumint => \&_gen_int,
12 integer => \&_gen_int,
13 bigint => \&_gen_int,
15 float => \&_gen_real,
16 decimal => \&_gen_real,
17 double_precision => \&_gen_real,
19 timestamp => \&_gen_date,
20 datetime => \&_gen_date,
21 date => \&_gen_date,
23 char => \&_gen_text,
24 varchar => \&_gen_text,
25 tinytext => \&_gen_text,
26 text => \&_gen_text,
27 mediumtext => \&_gen_text,
28 longtext => \&_gen_text,
30 set => \&_gen_set_enum,
31 enum => \&_gen_set_enum,
33 tinyblob => \&_gen_blob,
34 mediumblob => \&_gen_blob,
35 blob => \&_gen_blob,
36 longblob => \&_gen_blob,
39 our $default_value = {
40 UserPermission => {
41 borrowernumber => {
42 surname => 'my surname',
43 address => 'my adress',
44 city => 'my city',
45 branchcode => {
46 branchcode => 'cB',
47 branchname => 'my branchname',
49 categorycode => {
50 categorycode => 'cC',
51 hidelostitems => 0,
52 category_type => 'A',
53 default_privacy => 'default',
55 privacy => 1,
57 module_bit => {
58 module_bit => {
59 bit => '10',
61 code => 'my code',
63 code => undef,
66 $default_value->{UserPermission}->{code} = $default_value->{UserPermission}->{module_bit};
69 sub new {
70 my ($class) = @_;
71 my $self = {};
72 bless( $self, $class );
74 $self->schema( Koha::Database->new()->schema );
75 $self->schema->storage->sql_maker->quote_char('`');
77 return $self;
80 sub schema {
81 my ($self, $schema) = @_;
83 if( defined( $schema ) ) {
84 $self->{schema} = $schema;
86 return $self->{schema};
89 sub clear {
90 my ($self, $params) = @_;
91 my $source = $self->schema->resultset( $params->{source} );
92 return $source->delete_all();
95 sub build {
96 my ($self, $params) = @_;
97 my $source = $params->{source} || return;
98 my $value = $params->{value};
99 my $only_fk = $params->{only_fk} || 0;
101 my $col_values = $self->_buildColumnValues({
102 source => $source,
103 value => $value,
106 my $data;
107 my $foreign_keys = $self->_getForeignKeys( { source => $source } );
108 for my $fk ( @$foreign_keys ) {
109 my $fk_value;
110 my $col_name = $fk->{keys}->[0]->{col_name};
111 if( ref( $col_values->{$col_name} ) eq 'HASH' ) {
112 $fk_value = $col_values->{$col_name};
114 elsif( defined( $col_values->{$col_name} ) ) {
115 next;
118 my $fk_row = $self->build({
119 source => $fk->{source},
120 value => $fk_value,
123 my $keys = $fk->{keys};
124 for my $key( @$keys ) {
125 $col_values->{ $key->{col_name} } = $fk_row->{ $key->{col_fk_name} };
126 $data->{ $key->{col_name} } = $fk_row;
130 my $new_row;
131 if( $only_fk ) {
132 $new_row = $col_values;
134 else {
135 $new_row = $self->_storeColumnValues({
136 source => $source,
137 values => $col_values,
140 $new_row->{_fk} = $data if( defined( $data ) );
141 return $new_row;
144 sub _formatSource {
145 my ($params) = @_;
146 my $source = $params->{source};
147 $source =~ s|(\w+)$|$1|;
148 return $source;
151 sub _buildColumnValues {
152 my ($self, $params) = @_;
153 my $source = _formatSource( { source => $params->{source} } );
154 my $original_value = $params->{value};
156 my $col_values;
157 my @columns = $self->schema->source($source)->columns;
158 my %unique_constraints = $self->schema->source($source)->unique_constraints();
160 my $build_value = 1;
161 BUILD_VALUE: while ( $build_value ) {
162 # generate random values for all columns
163 for my $col_name( @columns ) {
164 my $col_value = $self->_buildColumnValue({
165 source => $source,
166 column_name => $col_name,
167 value => $original_value,
169 $col_values->{$col_name} = $col_value if( defined( $col_value ) );
171 $build_value = 0;
173 # If default values are set, maybe the data exist in the DB
174 # But no need to wait for another value
175 # FIXME this can be wrong if a default value is defined for a field
176 # which is not a constraint and that the generated value for the
177 # constraint already exists.
178 last BUILD_VALUE if exists( $default_value->{$source} );
180 # If there is no original value given and unique constraints exist,
181 # check if the generated values do not exist yet.
182 if ( not defined $original_value and scalar keys %unique_constraints > 0 ) {
184 # verify the data would respect each unique constraint
185 CONSTRAINTS: foreach my $constraint (keys %unique_constraints) {
187 my $condition;
188 my $constraint_columns = $unique_constraints{$constraint};
189 # loop through all constraint columns and build the condition
190 foreach my $constraint_column ( @$constraint_columns ) {
191 # build the filter
192 $condition->{ $constraint_column } =
193 $col_values->{ $constraint_column };
196 my $count = $self->schema
197 ->resultset( $source )
198 ->search( $condition )
199 ->count();
200 if ( $count > 0 ) {
201 # no point checking more stuff, exit the loop
202 $build_value = 1;
203 last CONSTRAINTS;
208 return $col_values;
211 # Returns [ {
212 # rel_name => $rel_name,
213 # source => $table_name,
214 # keys => [ {
215 # col_name => $col_name,
216 # col_fk_name => $col_fk_name,
217 # }, ... ]
218 # }, ... ]
219 sub _getForeignKeys {
220 my ($self, $params) = @_;
221 my $source = $self->schema->source( $params->{source} );
223 my @foreign_keys = ();
224 my @relationships = $source->relationships;
225 for my $rel_name( @relationships ) {
226 my $rel_info = $source->relationship_info($rel_name);
227 if( $rel_info->{attrs}->{is_foreign_key_constraint} ) {
228 my $rel = {
229 rel_name => $rel_name,
230 source => $rel_info->{source},
233 my @keys = ();
234 while( my ($col_fk_name, $col_name) = each(%{$rel_info->{cond}}) ) {
235 $col_name =~ s|self.(\w+)|$1|;
236 $col_fk_name =~ s|foreign.(\w+)|$1|;
237 push @keys, {
238 col_name => $col_name,
239 col_fk_name => $col_fk_name,
242 $rel->{keys} = \@keys;
244 push @foreign_keys, $rel;
247 return \@foreign_keys;
250 sub _storeColumnValues {
251 my ($self, $params) = @_;
252 my $source = $params->{source};
253 my $col_values = $params->{values};
255 my $new_row;
256 eval {
257 $new_row = $self->schema->resultset($source)->update_or_create($col_values);
259 die "$source - $@\n" if ($@);
261 eval {
262 $new_row = { $new_row->get_columns };
264 warn "$source - $@\n" if ($@);
265 return $new_row;
268 sub _buildColumnValue {
269 my ($self, $params) = @_;
270 my $source = $params->{source};
271 my $value = $params->{value};
272 my $col_name = $params->{column_name};
273 my $col_info = $self->schema->source($source)->column_info($col_name);
275 my $col_value;
276 if( exists( $value->{$col_name} ) ) {
277 $col_value = $value->{$col_name};
279 elsif( exists $default_value->{$source} and exists $default_value->{$source}->{$col_name} ) {
280 $col_value = $default_value->{$source}->{$col_name};
282 elsif( not $col_info->{default_value} and not $col_info->{is_auto_increment} and not $col_info->{is_foreign_key} ) {
283 eval {
284 my $data_type = $col_info->{data_type};
285 $data_type =~ s| |_|;
286 $col_value = $gen_type->{$data_type}->( $self, { info => $col_info } );
288 die "The type $col_info->{data_type} is not defined\n" if ($@);
290 return $col_value;
294 sub _gen_int {
295 my ($self, $params) = @_;
296 my $data_type = $params->{info}->{data_type};
298 my $max = 1;
299 if( $data_type eq 'tinyint' ) {
300 $max = 127;
302 elsif( $data_type eq 'smallint' ) {
303 $max = 32767;
305 elsif( $data_type eq 'mediumint' ) {
306 $max = 8388607;
308 elsif( $data_type eq 'integer' ) {
309 $max = 2147483647;
311 elsif( $data_type eq 'bigint' ) {
312 $max = 9223372036854775807;
314 return int( rand($max+1) );
317 sub _gen_real {
318 my ($self, $params) = @_;
319 my $max = 10 ** 38;
320 if( defined( $params->{info}->{size} ) ) {
321 $max = 10 ** ($params->{info}->{size}->[0] - $params->{info}->{size}->[1]);
323 return rand($max) + 1;
326 sub _gen_date {
327 my ($self, $params) = @_;
328 return $self->schema->storage->datetime_parser->format_datetime(DateTime->now());
331 sub _gen_text {
332 my ($self, $params) = @_;
333 # From perldoc String::Random
334 # max: specify the maximum number of characters to return for * and other
335 # regular expression patters that don't return a fixed number of characters
336 my $regex = '[A-Za-z][A-Za-z0-9_]*';
337 my $size = $params->{info}{size};
338 if ( defined $size and $size > 1 ) {
339 $size--;
340 } elsif ( defined $size and $size == 1 ) {
341 $regex = '[A-Za-z]';
343 my $random = String::Random->new( max => $size );
344 return $random->randregex($regex);
347 sub _gen_set_enum {
348 my ($self, $params) = @_;
349 return $params->{info}->{extra}->{list}->[0];
352 sub _gen_blob {
353 my ($self, $params) = @_;;
354 return 'b';
357 =head1 NAME
359 t::lib::TestBuilder.pm - Koha module to simplify the writing of tests
361 =head1 SYNOPSIS
363 use t::lib::TestBuilder;
365 Koha module to insert the foreign keys automatically for the tests
367 =head1 DESCRIPTION
369 This module allows to insert automatically an entry in the database. All the database changes are wrapped in a transaction.
370 The foreign keys are created according to the DBIx::Class schema.
371 The taken values are the values by default if it is possible or randomly generated.
373 =head1 FUNCTIONS
375 =head2 new
377 $builder = t::lib::TestBuilder->new()
379 Constructor - Begins a transaction and returns the object TestBuilder
381 =head2 schema
383 $schema = $builder->schema
385 Getter - Returns the schema of DBIx::Class
387 =head2 clear
389 $builder->clear({ source => $source_name })
391 =over
393 =item C<$source_name> is the name of the source in the DBIx::Class schema (required)
395 =back
397 Clears all the data of this source (database table)
399 =head2 build
401 $builder->build({
402 source => $source_name,
403 value => $value,
404 only_fk => $only_fk,
407 =over
409 =item C<$source_name> is the name of the source in the DBIx::Class schema (required)
411 =item C<$value> is the values for the entry (optional)
413 =item C<$only_fk> is a boolean to indicate if only the foreign keys are created (optional)
415 =back
417 Inserts an entry in the database by instanciating all the foreign keys.
418 The values can be specified, the values which are not given are default values if they exists or generated randomly.
419 Returns the values of the entry as a hashref with an extra key : _fk which contains all the values of the generated foreign keys.
421 =head1 AUTHOR
423 Yohann Dufour <yohann.dufour@biblibre.com>
425 =head1 COPYRIGHT
427 Copyright 2014 - Biblibre SARL
429 =head1 LICENSE
431 This file is part of Koha.
433 Koha is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
434 the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
436 Koha is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
438 You should have received a copy of the GNU General Public License along with Koha; if not, see <http://www.gnu.org/licenses>.
440 =cut