2 use base qw(Test::Class);
7 eval "use Test::Class";
8 plan skip_all => "Test::Class required for performing database tests" if $@;
9 # Or, maybe I should just die there.
13 use C4::Bookseller qw( AddBookseller );
20 use File::Temp qw/ tempdir /;
24 # Since this is an abstract base class, this prevents these tests from
25 # being run directly unless we're testing a subclass. It just makes
27 __PACKAGE__->SKIP_CLASS( 1 );
30 if ($ENV{SINGLE_TEST}) {
31 # if we're running the tests in one
32 # or more test files specified via
34 # make test-single TEST_FILES=lib/KohaTest/Foo.pm
36 # use this INIT trick taken from the POD for
39 Test::Class->runtests;
44 use Attribute::Handlers;
46 =head2 Expensive test method attribute
48 If a test method is decorated with an Expensive
49 attribute, it is skipped unless the RUN_EXPENSIVE_TESTS
50 environment variable is defined.
52 To declare an entire test class and its subclasses expensive,
53 define a SKIP_CLASS with the Expensive attribute:
55 sub SKIP_CLASS : Expensive { }
59 sub Expensive : ATTR(CODE) {
60 my ($package, $symbol, $sub, $attr, $data, $phase) = @_;
61 my $name = *{$symbol}{NAME};
62 if ($name eq 'SKIP_CLASS') {
63 if ($ENV{'RUN_EXPENSIVE_TESTS'}) {
64 *{$symbol} = sub { 0; }
66 *{$symbol} = sub { "Skipping expensive test classes $package (and subclasses)"; }
69 unless ($ENV{'RUN_EXPENSIVE_TESTS'}) {
70 # a test method that runs no tests and just returns a scalar is viewed by Test::Class as a skip
71 *{$symbol} = sub { "Skipping expensive test $package\:\:$name"; }
76 =head2 startup methods
78 these are run once, at the beginning of the whole test suite
82 sub startup_15_truncate_tables : Test( startup => 1 ) {
85 # my @truncate_tables = qw( accountlines
96 # auth_subfield_structure
122 # import_record_matches
132 # language_descriptions
133 # language_rfc4646_to_iso639
134 # language_script_bidi
135 # language_script_mapping
136 # language_subtag_registry
139 # marc_subfield_structure
142 # matcher_matchpoints
143 # matchpoint_component_norms
144 # matchpoint_components
155 # repeatable_holidays
171 # subscriptionhistory
172 # subscriptionroutinglist
177 # virtualshelfcontents
183 my @truncate_tables = qw( accountlines
226 subscriptionroutinglist
232 my $failed_to_truncate = 0;
233 foreach my $table ( @truncate_tables ) {
234 my $dbh = C4::Context->dbh();
235 $dbh->do( "truncate $table" )
236 or $failed_to_truncate = 1;
238 is( $failed_to_truncate, 0, 'truncated tables' );
241 =head2 startup_20_add_bookseller
243 we need a bookseller for many of the tests, so let's insert one. Feel
244 free to use this one, or insert your own.
248 sub startup_20_add_bookseller : Test(startup => 1) {
251 my $booksellerinfo = { name => 'bookseller ' . $self->random_string(),
254 my $id = AddBookseller( $booksellerinfo );
255 ok( $id, "created bookseller: $id" );
256 $self->{'booksellerid'} = $id;
261 =head2 startup_22_add_bookfund
263 we need a bookfund for many of the tests. This currently uses one that
264 is in the skeleton database. free to use this one, or insert your
267 sub startup_22_add_bookfund : Test(startup => 2) {
270 my $bookfundid = 'GEN';
271 my $bookfund = GetBookFund( $bookfundid, undef );
272 # diag( Data::Dumper->Dump( [ $bookfund ], qw( bookfund ) ) );
273 is( $bookfund->{'bookfundid'}, $bookfundid, "found bookfund: '$bookfundid'" );
274 is( $bookfund->{'bookfundname'}, 'General Stacks', "found bookfund: '$bookfundid'" );
276 $self->{'bookfundid'} = $bookfundid;
282 =head2 startup_24_add_branch
286 sub startup_24_add_branch : Test(startup => 1) {
291 branchcode => $self->random_string(3),
292 branchname => $self->random_string(),
293 branchaddress1 => $self->random_string(),
294 branchaddress2 => $self->random_string(),
295 branchaddress3 => $self->random_string(),
296 branchphone => $self->random_phone(),
297 branchfax => $self->random_phone(),
298 brancemail => $self->random_email(),
299 branchip => $self->random_ip(),
300 branchprinter => $self->random_string(),
302 C4::Branch::ModBranch($branch_info);
303 $self->{'branchcode'} = $branch_info->{'branchcode'};
304 ok( $self->{'branchcode'}, "created branch: $self->{'branchcode'}" );
308 =head2 startup_24_add_member
310 Add a patron/member for the tests to use
314 sub startup_24_add_member : Test(startup => 1) {
317 my $memberinfo = { surname => 'surname ' . $self->random_string(),
318 firstname => 'firstname' . $self->random_string(),
319 address => 'address' . $self->random_string(),
320 city => 'city' . $self->random_string(),
321 cardnumber => 'card' . $self->random_string(),
322 branchcode => 'CPL', # CPL => Centerville
323 categorycode => 'PT', # PT => PaTron
324 dateexpiry => '2010-01-01',
325 password => 'testpassword',
326 dateofbirth => $self->random_date(),
329 my $borrowernumber = AddMember( %$memberinfo );
330 ok( $borrowernumber, "created member: $borrowernumber" );
331 $self->{'memberid'} = $borrowernumber;
336 =head2 startup_30_login
340 sub startup_30_login : Test( startup => 2 ) {
343 $self->{'sessionid'} = '12345678'; # does this value matter?
344 my $borrower_details = C4::Members::GetMemberDetails( $self->{'memberid'} );
345 ok( $borrower_details->{'cardnumber'}, 'cardnumber' );
347 # make a cookie and force it into $cgi.
348 # This would be a lot easier with Test::MockObject::Extends.
349 my $cgi = CGI->new( { userid => $borrower_details->{'cardnumber'},
350 password => 'testpassword' } );
351 my $setcookie = $cgi->cookie( -name => 'CGISESSID',
352 -value => $self->{'sessionid'} );
353 $cgi->{'.cookies'} = { CGISESSID => $setcookie };
354 is( $cgi->cookie('CGISESSID'), $self->{'sessionid'}, 'the CGISESSID cookie is set' );
355 # diag( Data::Dumper->Dump( [ $cgi->cookie('CGISESSID') ], [ qw( cookie ) ] ) );
357 # C4::Auth::checkauth sometimes emits a warning about unable to append to sessionlog. That's OK.
358 my ( $userid, $cookie, $sessionID ) = C4::Auth::checkauth( $cgi, 'noauth', {}, 'intranet' );
359 # diag( Data::Dumper->Dump( [ $userid, $cookie, $sessionID ], [ qw( userid cookie sessionID ) ] ) );
361 # my $session = C4::Auth::get_session( $sessionID );
362 # diag( Data::Dumper->Dump( [ $session ], [ qw( session ) ] ) );
369 setup methods are run before every test method
373 =head2 teardown methods
375 teardown methods are many time, once at the end of each test method.
379 =head2 shutdown methods
381 shutdown methods are run once, at the end of the test suite
385 =head2 utility methods
387 These are not test methods, but they're handy
393 Nice for generating names and such. It's not actually random, more
401 my $wordsize = shift || 6; # how many letters in your string?
403 # leave out these characters: "oOlL10". They're too confusing.
404 my @alphabet = ( 'a'..'k','m','n','p'..'z', 'A'..'K','M','N','P'..'Z', 2..9 );
407 foreach ( 0..$wordsize ) {
408 $randomstring .= $alphabet[ rand( scalar( @alphabet ) ) ];
410 return $randomstring;
416 generates a random phone number. Currently, it's not actually random. It's an unusable US phone number
423 return '212-555-5555';
429 generates a random email address. They're all in the unusable
430 'example.com' domain that is designed for this purpose.
437 return $self->random_string() . '@example.com';
443 returns an IP address suitable for testing purposes.
456 returns a somewhat random date in the iso (yyyy-mm-dd) format.
463 my $year = 1800 + int( rand(300) ); # 1800 - 2199
464 my $month = 1 + int( rand(12) ); # 1 - 12
465 my $day = 1 + int( rand(28) ); # 1 - 28
466 # stop at the 28th to keep us from generating February 31st and such.
468 return sprintf( '%04d-%02d-%02d', $year, $month, $day );
474 returns tomorrow's date as YYYY-MM-DD.
481 return $self->days_from_now( 1 );
487 returns yesterday's date as YYYY-MM-DD.
494 return $self->days_from_now( -1 );
500 returns an arbitrary date based on today in YYYY-MM-DD format.
506 my $days = shift or return;
508 my $seconds = time + $days * 60*60*24;
509 my $yyyymmdd = sprintf( '%04d-%02d-%02d',
510 localtime( $seconds )->year() + 1900,
511 localtime( $seconds )->mon() + 1,
512 localtime( $seconds )->mday() );
518 $self->add_biblios( count => 10,
522 count: number of biblios to add
523 add_items: should you add items for each one?
529 adds the biblionumbers to the $self->{'biblios'} listref
532 Should I allow you to pass in biblio information, like title?
533 Since this method is in the KohaTest class, all tests in it will be ignored, unless you call this from your own namespace.
534 This runs 10 tests, plus 4 for each "count", plus 3 more for each item added.
542 $param{'count'} = 1 unless defined( $param{'count'} );
543 $param{'add_items'} = 0 unless defined( $param{'add_items'} );
545 foreach my $counter ( 1..$param{'count'} ) {
546 my $marcrecord = MARC::Record->new();
547 isa_ok( $marcrecord, 'MARC::Record' );
548 my @marc_fields = ( MARC::Field->new( '100', '1', '0',
551 MARC::Field->new( '245', '1', '4',
552 a => sprintf( 'The Adventures of Huckleberry Finn Test %s', $counter ),
553 c => "Mark Twain ; illustrated by E.W. Kemble." ),
554 MARC::Field->new( '952', '0', '0',
555 p => '12345678' . $self->random_string() ), # barcode
556 MARC::Field->new( '952', '0', '0',
557 o => $self->random_string() ), # callnumber
558 MARC::Field->new( '952', '0', '0',
563 my $appendedfieldscount = $marcrecord->append_fields( @marc_fields );
565 diag $MARC::Record::ERROR if ( $MARC::Record::ERROR );
566 is( $appendedfieldscount, scalar @marc_fields, 'added correct number of MARC fields' );
568 my $frameworkcode = ''; # XXX I'd like to put something reasonable here.
569 my ( $biblionumber, $biblioitemnumber ) = AddBiblio( $marcrecord, $frameworkcode );
570 ok( $biblionumber, "the biblionumber is $biblionumber" );
571 ok( $biblioitemnumber, "the biblioitemnumber is $biblioitemnumber" );
572 if ( $param{'add_items'} ) {
573 # my @iteminfo = AddItem( {}, $biblionumber );
574 my @iteminfo = AddItemFromMarc( $marcrecord, $biblionumber );
575 is( $iteminfo[0], $biblionumber, "biblionumber is $biblionumber" );
576 is( $iteminfo[1], $biblioitemnumber, "biblioitemnumber is $biblioitemnumber" );
577 ok( $iteminfo[2], "itemnumber is $iteminfo[2]" );
578 push @{ $self->{'items'} },
579 { biblionumber => $iteminfo[0],
580 biblioitemnumber => $iteminfo[1],
581 itemnumber => $iteminfo[2],
584 push @{$self->{'biblios'}}, $biblionumber;
587 $self->reindex_marc();
588 my $query = 'Finn Test';
589 my ( $error, $results, undef ) = SimpleSearch( $query );
590 if ( !defined $error && $param{'count'} <= @{$results} ) {
591 pass( "found all $param{'count'} titles" );
593 fail( "we never found all $param{'count'} titles" );
600 Do a fast reindexing of all of the bib and authority
601 records and mark all zebraqueue entries done.
603 Useful for test routines that need to do a
604 lot of indexing without having to wait for
612 # mark zebraqueue done regardless of the indexing mode
613 my $dbh = C4::Context->dbh();
614 $dbh->do("UPDATE zebraqueue SET done = 1 WHERE done = 0");
616 my $directory = tempdir(CLEANUP => 1);
617 foreach my $record_type qw(biblio authority) {
618 mkdir "$directory/$record_type";
619 my $sth = $dbh->prepare($record_type eq "biblio" ? "SELECT marc FROM biblioitems" : "SELECT marc FROM auth_header");
621 open my $out, '>:encoding(UTF-8)', "$directory/$record_type/records";
622 while (my ($blob) = $sth->fetchrow_array) {
626 my $zebra_server = "${record_type}server";
627 my $zebra_config = C4::Context->zebraconfig($zebra_server)->{'config'};
628 my $zebra_db_dir = C4::Context->zebraconfig($zebra_server)->{'directory'};
629 my $zebra_db = $record_type eq 'biblio' ? 'biblios' : 'authorities';
630 system "zebraidx -c $zebra_config -d $zebra_db -g iso2709 init > /dev/null 2>\&1";
631 system "zebraidx -c $zebra_config -d $zebra_db -g iso2709 update $directory/${record_type} > /dev/null 2>\&1";
632 system "zebraidx -c $zebra_config -d $zebra_db -g iso2709 commit > /dev/null 2>\&1";
638 =head3 clear_test_database
640 removes all tables from test database so that install starts with a clean slate
644 sub clear_test_database {
646 diag "removing tables from test database";
648 my $dbh = C4::Context->dbh;
649 my $schema = C4::Context->config("database");
651 my @tables = get_all_tables($dbh, $schema);
652 foreach my $table (@tables) {
653 drop_all_foreign_keys($dbh, $table);
656 foreach my $table (@tables) {
657 drop_table($dbh, $table);
662 my ($dbh, $schema) = @_;
663 my $sth = $dbh->prepare("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?");
665 $sth->execute($schema);
666 while (my ($table) = $sth->fetchrow_array) {
667 push @tables, $table;
673 sub drop_all_foreign_keys {
674 my ($dbh, $table) = @_;
675 # get the table description
676 my $sth = $dbh->prepare("SHOW CREATE TABLE $table");
678 my $vsc_structure = $sth->fetchrow;
679 # split on CONSTRAINT keyword
680 my @fks = split /CONSTRAINT /,$vsc_structure;
683 # isolate what is before FOREIGN KEY, if there is something, it's a foreign key to drop
684 $_ = /(.*) FOREIGN KEY.*/;
687 # we have found 1 foreign, drop it
688 $dbh->do("ALTER TABLE $table DROP FOREIGN KEY $id");
690 diag "unable to DROP FOREIGN KEY '$id' on TABLE '$table' due to: " . $dbh->errstr();
698 my ($dbh, $table) = @_;
699 $dbh->do("DROP TABLE $table");
701 diag "unable to drop table: '$table' due to: " . $dbh->errstr();
705 =head3 create_test_database
707 sets up the test database.
711 sub create_test_database {
713 diag 'creating testing database...';
714 my $installer = C4::Installer->new() or die 'unable to create new installer';
715 # warn Data::Dumper->Dump( [ $installer ], [ 'installer' ] );
716 my $all_languages = getAllLanguages();
717 my $error = $installer->load_db_schema();
718 die "unable to load_db_schema: $error" if ( $error );
719 my $list = $installer->sql_file_list('en', 'marc21', { optional => 1,
721 my ($fwk_language, $installed_list) = $installer->load_sql_in_order($all_languages, @$list);
722 $installer->set_version_syspref();
723 $installer->set_marcflavour_syspref('MARC21');
724 diag 'database created.'
728 =head3 start_zebrasrv
730 This method deletes and reinitializes the zebra database directory,
731 and then spans off a zebra server.
738 diag 'cleaning zebrasrv...';
740 foreach my $zebra_server ( qw( biblioserver authorityserver ) ) {
741 my $zebra_config = C4::Context->zebraconfig($zebra_server)->{'config'};
742 my $zebra_db_dir = C4::Context->zebraconfig($zebra_server)->{'directory'};
743 foreach my $zebra_db_name ( qw( biblios authorities ) ) {
744 my $command = "zebraidx -c $zebra_config -d $zebra_db_name init";
745 my $return = system( $command . ' > /dev/null 2>&1' );
746 if ( $return != 0 ) {
747 diag( "command '$command' died with value: " . $? >> 8 );
750 $command = "zebraidx -c $zebra_config -d $zebra_db_name create $zebra_db_name";
752 $return = system( $command . ' > /dev/null 2>&1' );
753 if ( $return != 0 ) {
754 diag( "command '$command' died with value: " . $? >> 8 );
759 diag 'starting zebrasrv...';
761 my $pidfile = File::Spec->catdir( C4::Context->config("logdir"), 'zebra.pid' );
762 my $command = sprintf( 'zebrasrv -f %s -D -l %s -p %s',
764 File::Spec->catdir( C4::Context->config("logdir"), 'zebra.log' ),
768 my $output = qx( $command );
772 if ( -e $pidfile, 'pidfile exists' ) {
773 diag 'zebrasrv started.';
775 die 'unable to start zebrasrv';
782 using the PID file for the zebra server, send it a TERM signal with
783 "kill". We can't tell if the process actually dies or not.
789 my $pidfile = File::Spec->catdir( C4::Context->config("logdir"), 'zebra.pid' );
791 open( my $pidh, '<', $pidfile )
793 if ( defined $pidh ) {
794 my ( $pid ) = <$pidh> or return;
796 my $killed = kill 15, $pid; # 15 is TERM
797 if ( $killed != 1 ) {
798 warn "unable to kill zebrasrv with pid: $pid";
805 =head3 start_zebraqueue_daemon
807 kick off a zebraqueue_daemon.pl process.
811 sub start_zebraqueue_daemon {
813 my $command = q(run/bin/koha-index-daemon-ctl.sh start);
815 my $started = system( $command );
816 diag "started: $started";
820 =head3 stop_zebraqueue_daemon
825 sub stop_zebraqueue_daemon {
827 my $command = q(run/bin/koha-index-daemon-ctl.sh stop);
829 my $started = system( $command );
830 diag "started: $started";