Browse Source

Bug 12603: creating TestBuilder to simplify tests

This patch contains a new module t::lib::TestBuilder which allows to write tests easier and it contains the unit tests of this module.
For more information, see the documentation of the module.

This module uses the DBIx::Class schema and works with a clean DBIx::Class schema. In order to use it, you have to remove the current circular dependence (existing in the DBIx::Class) by applying the last patch of the bug 11518.

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Tomas Cohen Arazi <tomascohen@gmail.com>
3.20.x
Yohann Dufour 8 years ago
committed by Tomas Cohen Arazi
parent
commit
ec50dc7fd8
  1. 249
      t/db_dependent/TestBuilder.t
  2. 397
      t/lib/TestBuilder.pm

249
t/db_dependent/TestBuilder.t

@ -0,0 +1,249 @@
#!/usr/bin/perl
# This file is part of Koha.
#
# Copyright 2014 - Biblibre SARL
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use Test::More tests => 40;
BEGIN {
use_ok('t::lib::TestBuilder');
}
my $builder = t::lib::TestBuilder->new();
is( $builder->build(), undef, 'build without arguments returns undef' );
my @sources = $builder->schema->sources;
my $nb_failure = 0;
for my $source (@sources) {
eval { $builder->build( { source => $source } ); };
$nb_failure++ if ($@);
}
is( $nb_failure, 0, 'TestBuilder can create a entry for every sources' );
my $my_overduerules_transport_type = {
message_transport_type => {
message_transport_type => 'my msg_t_t',
},
letternumber => 1,
branchcode => {
branchcode => 'codeB',
categorycode => 'codeC',
},
categorycode => undef,
};
$my_overduerules_transport_type->{categorycode} = $my_overduerules_transport_type->{branchcode};
my $overduerules_transport_type = $builder->build({
source => 'OverduerulesTransportType',
value => $my_overduerules_transport_type,
});
is(
$overduerules_transport_type->{message_transport_type},
$my_overduerules_transport_type->{message_transport_type}->{message_transport_type},
'build stores the message_transport_type correctly'
);
is(
$overduerules_transport_type->{letternumber},
$my_overduerules_transport_type->{letternumber},
'build stores the letternumber correctly'
);
is(
$overduerules_transport_type->{branchcode},
$my_overduerules_transport_type->{branchcode}->{branchcode},
'build stores the branchcode correctly'
);
is(
$overduerules_transport_type->{categorycode},
$my_overduerules_transport_type->{categorycode}->{categorycode},
'build stores the categorycode correctly'
);
is(
$overduerules_transport_type->{_fk}->{message_transport_type}->{message_transport_type},
$my_overduerules_transport_type->{message_transport_type}->{message_transport_type},
'build stores the foreign key message_transport_type correctly'
);
is(
$overduerules_transport_type->{_fk}->{branchcode}->{branchcode},
$my_overduerules_transport_type->{branchcode}->{branchcode},
'build stores the foreign key branchcode correctly'
);
is(
$overduerules_transport_type->{_fk}->{categorycode}->{categorycode},
$my_overduerules_transport_type->{categorycode}->{categorycode},
'build stores the foreign key categorycode correctly'
);
is_deeply(
$overduerules_transport_type->{_fk}->{branchcode},
$overduerules_transport_type->{_fk}->{categorycode},
'build links the branchcode and the categorycode correctly'
);
isnt(
$overduerules_transport_type->{_fk}->{branchcode}->{letter2},
undef,
'build generates values if they are not given'
);
my $my_user_permission = $t::lib::TestBuilder::default_value->{UserPermission};
my $user_permission = $builder->build({
source => 'UserPermission',
});
isnt(
$user_permission->{borrowernumber},
undef,
'build generates a borrowernumber correctly'
);
is(
$user_permission->{module_bit},
$my_user_permission->{module_bit}->{module_bit}->{bit},
'build stores the default value correctly'
);
is(
$user_permission->{code},
$my_user_permission->{module_bit}->{code},
'build stores the default value correctly'
);
is(
$user_permission->{borrowernumber},
$user_permission->{_fk}->{borrowernumber}->{borrowernumber},
'build links the foreign key correctly'
);
is(
$user_permission->{_fk}->{borrowernumber}->{surname},
$my_user_permission->{borrowernumber}->{surname},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{borrowernumber}->{address},
$my_user_permission->{borrowernumber}->{address},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{borrowernumber}->{city},
$my_user_permission->{borrowernumber}->{city},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{borrowernumber}->{_fk}->{branchcode}->{branchcode},
$my_user_permission->{borrowernumber}->{branchcode}->{branchcode},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{borrowernumber}->{_fk}->{branchcode}->{branchname},
$my_user_permission->{borrowernumber}->{branchcode}->{branchname},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{borrowernumber}->{_fk}->{categorycode}->{categorycode},
$my_user_permission->{borrowernumber}->{categorycode}->{categorycode},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{borrowernumber}->{_fk}->{categorycode}->{hidelostitems},
$my_user_permission->{borrowernumber}->{categorycode}->{hidelostitems},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{borrowernumber}->{_fk}->{categorycode}->{category_type},
$my_user_permission->{borrowernumber}->{categorycode}->{category_type},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{borrowernumber}->{_fk}->{categorycode}->{defaultprivacy},
$my_user_permission->{borrowernumber}->{categorycode}->{defaultprivacy},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{borrowernumber}->{privacy},
$my_user_permission->{borrowernumber}->{privacy},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{module_bit}->{_fk}->{module_bit}->{bit},
$my_user_permission->{module_bit}->{module_bit}->{bit},
'build stores the foreign key value correctly'
);
is(
$user_permission->{_fk}->{module_bit}->{code},
$my_user_permission->{module_bit}->{code},
'build stores the foreign key value correctly'
);
is_deeply(
$user_permission->{_fk}->{module_bit},
$user_permission->{_fk}->{code},
'build links the codes correctly'
);
isnt(
$user_permission->{_fk}->{borrowernumber}->{cardnumber},
undef,
'build generates values if they are not given'
);
isnt(
$user_permission->{_fk}->{borrowernumber}->{_fk}->{branchcode}->{branchaddress1},
undef,
'build generates values if they are not given'
);
isnt(
$user_permission->{_fk}->{borrowernumber}->{_fk}->{categorycode}->{description},
undef,
'build generates values if they are not given'
);
isnt(
$user_permission->{_fk}->{module_bit}->{description},
undef,
'build generates values if they are not given'
);
isnt(
$user_permission->{_fk}->{module_bit}->{_fk}->{module_bit}->{flag},
undef,
'build generates values if they are not given'
);
my $nb_basket = $builder->schema->resultset('Aqbasket')->search();
isnt( $nb_basket, 0, 'add stores the generated entries correctly' );
$builder->clear( { source => 'Aqbasket' } );
$nb_basket = $builder->schema->resultset('Aqbasket')->search();
is( $nb_basket, 0, 'clear removes all the entries correctly' );
my $rs_aqbookseller = $builder->schema->resultset('Aqbookseller');
my $bookseller = $builder->build({
source => 'Aqbookseller',
only_fk => 1,
});
delete $bookseller->{_fk};
my $bookseller_from_db = $rs_aqbookseller->find($bookseller);
is( $bookseller_from_db, undef, 'build with only_fk = 1 does not store the entry' );
my $bookseller_result = $rs_aqbookseller->create($bookseller);
is( $bookseller_result->in_storage, 1, 'build with only_fk = 1 creates the foreign keys correctly' );
$bookseller = $builder->build({
source => 'Aqbookseller',
});
delete $bookseller->{_fk};
$bookseller_from_db = $rs_aqbookseller->find($bookseller);
is( $bookseller_from_db->in_storage, 1, 'build without the parameter only_sk stores the entry correctly' );
$bookseller = $builder->build({
source => 'Aqbookseller',
only_fk => 0,
});
delete $bookseller->{_fk};
$bookseller_from_db = $rs_aqbookseller->find($bookseller);
is( $bookseller_from_db->in_storage, 1, 'build with only_fk = 0 stores the entry correctly' );

397
t/lib/TestBuilder.pm

@ -0,0 +1,397 @@
package t::lib::TestBuilder;
use Modern::Perl;
use Koha::Database;
use String::Random;
my $gen_type = {
tinyint => \&_gen_int,
smallint => \&_gen_int,
mediumint => \&_gen_int,
integer => \&_gen_int,
bigint => \&_gen_int,
float => \&_gen_real,
decimal => \&_gen_real,
double_precision => \&_gen_real,
timestamp => \&_gen_date,
datetime => \&_gen_date,
date => \&_gen_date,
char => \&_gen_text,
varchar => \&_gen_text,
tinytext => \&_gen_text,
text => \&_gen_text,
mediumtext => \&_gen_text,
longtext => \&_gen_text,
set => \&_gen_set_enum,
enum => \&_gen_set_enum,
tinyblob => \&_gen_blob,
mediumblob => \&_gen_blob,
blob => \&_gen_blob,
longblob => \&_gen_blob,
};
our $default_value = {
UserPermission => {
borrowernumber => {
surname => 'my surname',
address => 'my adress',
city => 'my city',
branchcode => {
branchcode => 'cB',
branchname => 'my branchname',
},
categorycode => {
categorycode => 'cC',
hidelostitems => 0,
category_type => 'A',
default_privacy => 'default',
},
privacy => 1,
},
module_bit => {
module_bit => {
bit => '10',
},
code => 'my code',
},
code => undef,
},
};
$default_value->{UserPermission}->{code} = $default_value->{UserPermission}->{module_bit};
sub new {
my ($class) = @_;
my $self = {};
bless( $self, $class );
$self->schema( Koha::Database->new()->schema );
$self->schema->txn_begin();
$self->schema->storage->sql_maker->quote_char('`');
return $self;
}
sub schema {
my ($self, $schema) = @_;
if( defined( $schema ) ) {
$self->{schema} = $schema;
}
return $self->{schema};
}
sub clear {
my ($self, $params) = @_;
my $source = $self->schema->resultset( $params->{source} );
return $source->delete_all();
}
sub build {
my ($self, $params) = @_;
my $source = $params->{source} || return;
my $value = $params->{value};
my $only_fk = $params->{only_fk} || 0;
my $col_values = $self->_buildColumnValues({
source => $source,
value => $value,
});
my $data;
my $foreign_keys = $self->_getForeignKeys( { source => $source } );
for my $fk ( @$foreign_keys ) {
my $fk_value;
my $col_name = $fk->{keys}->[0]->{col_name};
if( ref( $col_values->{$col_name} ) eq 'HASH' ) {
$fk_value = $col_values->{$col_name};
}
elsif( defined( $col_values->{$col_name} ) ) {
next;
}
my $fk_row = $self->build({
source => $fk->{source},
value => $fk_value,
});
my $keys = $fk->{keys};
for my $key( @$keys ) {
$col_values->{ $key->{col_name} } = $fk_row->{ $key->{col_fk_name} };
$data->{ $key->{col_name} } = $fk_row;
}
}
my $new_row;
if( $only_fk ) {
$new_row = $col_values;
}
else {
$new_row = $self->_storeColumnValues({
source => $source,
values => $col_values,
});
}
$new_row->{_fk} = $data if( defined( $data ) );
return $new_row;
}
sub _formatSource {
my ($params) = @_;
my $source = $params->{source};
$source =~ s|(\w+)$|$1|;
return $source;
}
sub _buildColumnValues {
my ($self, $params) = @_;
my $source = _formatSource( { source => $params->{source} } );
my $value = $params->{value};
my $col_values;
my @columns = $self->schema->source($source)->columns;
for my $col_name( @columns ) {
my $col_value = $self->_buildColumnValue({
source => $source,
column_name => $col_name,
value => $value,
});
$col_values->{$col_name} = $col_value if( defined( $col_value ) );
}
return $col_values;
}
# Returns [ {
# rel_name => $rel_name,
# source => $table_name,
# keys => [ {
# col_name => $col_name,
# col_fk_name => $col_fk_name,
# }, ... ]
# }, ... ]
sub _getForeignKeys {
my ($self, $params) = @_;
my $source = $self->schema->source( $params->{source} );
my @foreign_keys = ();
my @relationships = $source->relationships;
for my $rel_name( @relationships ) {
my $rel_info = $source->relationship_info($rel_name);
if( $rel_info->{attrs}->{is_foreign_key_constraint} ) {
my $rel = {
rel_name => $rel_name,
source => $rel_info->{source},
};
my @keys = ();
while( my ($col_fk_name, $col_name) = each($rel_info->{cond}) ) {
$col_name =~ s|self.(\w+)|$1|;
$col_fk_name =~ s|foreign.(\w+)|$1|;
push @keys, {
col_name => $col_name,
col_fk_name => $col_fk_name,
};
}
$rel->{keys} = \@keys;
push @foreign_keys, $rel;
}
}
return \@foreign_keys;
}
sub _storeColumnValues {
my ($self, $params) = @_;
my $source = $params->{source};
my $col_values = $params->{values};
my $new_row;
eval {
$new_row = $self->schema->resultset($source)->update_or_create($col_values);
};
die "$source - $@\n" if ($@);
eval {
$new_row = { $new_row->get_columns };
};
warn "$source - $@\n" if ($@);
return $new_row;
}
sub _buildColumnValue {
my ($self, $params) = @_;
my $source = $params->{source};
my $value = $params->{value};
my $col_name = $params->{column_name};
my $col_info = $self->schema->source($source)->column_info($col_name);
my $col_value;
if( exists( $value->{$col_name} ) ) {
$col_value = $value->{$col_name};
}
elsif( exists( $default_value->{$source}->{$col_name} ) ) {
$col_value = $default_value->{$source}->{$col_name};
}
elsif( not $col_info->{default_value} and not $col_info->{is_auto_increment} and not $col_info->{is_foreign_key} ) {
eval {
my $data_type = $col_info->{data_type};
$data_type =~ s| |_|;
$col_value = $gen_type->{$data_type}->( $self, { info => $col_info } );
};
die "The type $col_info->{data_type} is not defined\n" if ($@);
}
return $col_value;
}
sub _gen_int {
my ($self, $params) = @_;
my $data_type = $params->{info}->{data_type};
my $max = 1;
if( $data_type eq 'tinyint' ) {
$max = 127;
}
elsif( $data_type eq 'smallint' ) {
$max = 32767;
}
elsif( $data_type eq 'mediumint' ) {
$max = 8388607;
}
elsif( $data_type eq 'integer' ) {
$max = 2147483647;
}
elsif( $data_type eq 'bigint' ) {
$max = 9223372036854775807;
}
return int( rand($max+1) );
}
sub _gen_real {
my ($self, $params) = @_;
my $max = 10 ** 38;
if( defined( $params->{info}->{size} ) ) {
$max = 10 ** ($params->{info}->{size}->[0] - $params->{info}->{size}->[1]);
}
return rand($max) + 1;
}
sub _gen_date {
my ($self, $params) = @_;
return $self->schema->storage->datetime_parser->format_datetime(DateTime->now());
}
sub _gen_text {
my ($self, $params) = @_;
my $random = String::Random->new( max => $params->{info}->{size} );
return $random->randregex('[A-Za-z]+[A-Za-z0-9_]*');
}
sub _gen_set_enum {
my ($self, $params) = @_;
return $params->{info}->{extra}->{list}->[0];
}
sub _gen_blob {
my ($self, $params) = @_;;
return 'b';
}
sub DESTROY {
my $self = shift;
$self->schema->txn_rollback();
}
=head1 NAME
t::lib::TestBuilder.pm - Koha module to simplify the writing of tests
=head1 SYNOPSIS
use t::lib::TestBuilder;
Koha module to insert the foreign keys automatically for the tests
=head1 DESCRIPTION
This module allows to insert automatically an entry in the database. All the database changes are wrapped in a transaction.
The foreign keys are created according to the DBIx::Class schema.
The taken values are the values by default if it is possible or randomly generated.
=head1 FUNCTIONS
=head2 new
$builder = t::lib::TestBuilder->new()
Constructor - Begins a transaction and returns the object TestBuilder
=head2 schema
$schema = $builder->schema
Getter - Returns the schema of DBIx::Class
=head2 clear
$builder->clear({ source => $source_name })
=over
=item C<$source_name> is the name of the source in the DBIx::Class schema (required)
=back
Clears all the data of this source (database table)
=head2 build
$builder->build({
source => $source_name,
value => $value,
only_fk => $only_fk,
})
=over
=item C<$source_name> is the name of the source in the DBIx::Class schema (required)
=item C<$value> is the values for the entry (optional)
=item C<$only_fk> is a boolean to indicate if only the foreign keys are created (optional)
=back
Inserts an entry in the database by instanciating all the foreign keys.
The values can be specified, the values which are not given are default values if they exists or generated randomly.
Returns the values of the entry as a hashref with an extra key : _fk which contains all the values of the generated foreign keys.
=head1 AUTHOR
Yohann Dufour <yohann.dufour@biblibre.com>
=head1 COPYRIGHT
Copyright 2014 - Biblibre SARL
=head1 LICENSE
This file is part of Koha.
Koha is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
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.
You should have received a copy of the GNU General Public License along with Koha; if not, see <http://www.gnu.org/licenses>.
=cut
1;
Loading…
Cancel
Save