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.
21 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 single-test 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
97 # auth_subfield_structure
123 # import_record_matches
133 # language_descriptions
134 # language_rfc4646_to_iso639
135 # language_script_bidi
136 # language_script_mapping
137 # language_subtag_registry
140 # marc_subfield_structure
143 # matcher_matchpoints
144 # matchpoint_component_norms
145 # matchpoint_components
157 # repeatable_holidays
173 # subscriptionhistory
174 # subscriptionroutinglist
179 # virtualshelfcontents
185 my @truncate_tables = qw( accountlines
232 subscriptionroutinglist
238 my $failed_to_truncate = 0;
239 foreach my $table ( @truncate_tables ) {
240 my $dbh = C4::Context->dbh();
241 $dbh->do( "truncate $table" )
242 or $failed_to_truncate = 1;
244 is( $failed_to_truncate, 0, 'truncated tables' );
247 =head2 startup_20_add_bookseller
249 we need a bookseller for many of the tests, so let's insert one. Feel
250 free to use this one, or insert your own.
254 sub startup_20_add_bookseller : Test(startup => 1) {
257 my $booksellerinfo = { name => 'bookseller ' . $self->random_string(),
260 my $id = AddBookseller( $booksellerinfo );
261 ok( $id, "created bookseller: $id" );
262 $self->{'booksellerid'} = $id;
267 =head2 startup_22_add_bookfund
269 we need a bookfund for many of the tests. This currently uses one that
270 is in the skeleton database. free to use this one, or insert your
275 sub startup_22_add_bookfund : Test(startup => 2) {
278 my $bookfundid = 'GEN';
279 my $bookfund = GetBookFund( $bookfundid, undef );
280 # diag( Data::Dumper->Dump( [ $bookfund ], qw( bookfund ) ) );
281 is( $bookfund->{'bookfundid'}, $bookfundid, "found bookfund: '$bookfundid'" );
282 is( $bookfund->{'bookfundname'}, 'General Stacks', "found bookfund: '$bookfundid'" );
284 $self->{'bookfundid'} = $bookfundid;
288 =head2 startup_24_add_member
290 Add a patron/member for the tests to use
294 sub startup_24_add_member : Test(startup => 1) {
297 my $memberinfo = { surname => 'surname ' . $self->random_string(),
298 firstname => 'firstname' . $self->random_string(),
299 address => 'address' . $self->random_string(),
300 city => 'city' . $self->random_string(),
301 cardnumber => 'card' . $self->random_string(),
302 branchcode => 'CPL', # CPL => Centerville
303 categorycode => 'PT', # PT => PaTron
304 dateexpiry => '2010-01-01',
305 password => 'testpassword',
308 my $borrowernumber = AddMember( %$memberinfo );
309 ok( $borrowernumber, "created member: $borrowernumber" );
310 $self->{'memberid'} = $borrowernumber;
315 =head2 startup_30_login
319 sub startup_30_login : Test( startup => 2 ) {
322 $self->{'sessionid'} = '12345678'; # does this value matter?
323 my $borrower_details = C4::Members::GetMemberDetails( $self->{'memberid'} );
324 ok( $borrower_details->{'cardnumber'}, 'cardnumber' );
326 # make a cookie and force it into $cgi.
327 # This would be a lot easier with Test::MockObject::Extends.
328 my $cgi = CGI->new( { userid => $borrower_details->{'cardnumber'},
329 password => 'testpassword' } );
330 my $setcookie = $cgi->cookie( -name => 'CGISESSID',
331 -value => $self->{'sessionid'} );
332 $cgi->{'.cookies'} = { CGISESSID => $setcookie };
333 is( $cgi->cookie('CGISESSID'), $self->{'sessionid'}, 'the CGISESSID cookie is set' );
334 # diag( Data::Dumper->Dump( [ $cgi->cookie('CGISESSID') ], [ qw( cookie ) ] ) );
336 # C4::Auth::checkauth sometimes emits a warning about unable to append to sessionlog. That's OK.
337 my ( $userid, $cookie, $sessionID ) = C4::Auth::checkauth( $cgi, 'noauth', {}, 'intranet' );
338 # diag( Data::Dumper->Dump( [ $userid, $cookie, $sessionID ], [ qw( userid cookie sessionID ) ] ) );
340 # my $session = C4::Auth::get_session( $sessionID );
341 # diag( Data::Dumper->Dump( [ $session ], [ qw( session ) ] ) );
348 setup methods are run before every test method
352 =head2 teardown methods
354 teardown methods are many time, once at the end of each test method.
358 =head2 shutdown methods
360 shutdown methods are run once, at the end of the test suite
364 =head2 utility methods
366 These are not test methods, but they're handy
372 Nice for generating names and such. It's not actually random, more
380 my $wordsize = 6; # how many letters in your string?
382 # leave out these characters: "oOlL10". They're too confusing.
383 my @alphabet = ( 'a'..'k','m','n','p'..'z', 'A'..'K','M','N','P'..'Z', 2..9 );
386 foreach ( 0..$wordsize ) {
387 $randomstring .= $alphabet[ rand( scalar( @alphabet ) ) ];
389 return $randomstring;
395 $self->add_biblios( count => 10,
399 count: number of biblios to add
400 add_items: should you add items for each one?
406 adds the biblionumbers to the $self->{'biblios'} listref
409 Should I allow you to pass in biblio information, like title?
410 Since this method is in the KohaTest class, all tests in it will be ignored, unless you call this from your own namespace.
411 This runs 10 tests, plus 4 for each "count", plus 3 more for each item added.
419 $param{'count'} = 1 unless defined( $param{'count'} );
420 $param{'add_items'} = 0 unless defined( $param{'add_items'} );
422 foreach my $counter ( 1..$param{'count'} ) {
423 my $marcrecord = MARC::Record->new();
424 isa_ok( $marcrecord, 'MARC::Record' );
425 my $appendedfieldscount = $marcrecord->append_fields( MARC::Field->new( '100', '1', '0',
428 MARC::Field->new( '245', '1', '4',
429 a => sprintf( 'The Adventures of Huckleberry Finn Test %s', $counter ),
430 c => "Mark Twain ; illustrated by E.W. Kemble." ),
431 MARC::Field->new( '952', '0', '0',
432 p => '12345678' . $self->random_string() ), # barcode
433 MARC::Field->new( '952', '0', '0',
438 diag $MARC::Record::ERROR if ( $MARC::Record::ERROR );
439 is( $appendedfieldscount, 4, 'added 4 fields' );
441 my $frameworkcode = ''; # XXX I'd like to put something reasonable here.
442 my ( $biblionumber, $biblioitemnumber ) = AddBiblio( $marcrecord, $frameworkcode );
443 ok( $biblionumber, "the biblionumber is $biblionumber" );
444 ok( $biblioitemnumber, "the biblioitemnumber is $biblioitemnumber" );
445 if ( $param{'add_items'} ) {
446 # my @iteminfo = AddItem( {}, $biblionumber );
447 my @iteminfo = AddItemFromMarc( $marcrecord, $biblionumber );
448 is( $iteminfo[0], $biblionumber, "biblionumber is $biblionumber" );
449 is( $iteminfo[1], $biblioitemnumber, "biblioitemnumber is $biblioitemnumber" );
450 ok( $iteminfo[2], "itemnumber is $iteminfo[2]" );
452 push @{$self->{'biblios'}}, $biblionumber;
455 $self->reindex_marc();
456 my $query = 'Finn Test';
457 my ( $error, $results ) = SimpleSearch( $query );
458 if ( $param{'count'} <= scalar( @$results ) ) {
459 pass( "found all $param{'count'} titles" );
461 fail( "we never found all $param{'count'} titles" );
468 Do a fast reindexing of all of the bib and authority
469 records and mark all zebraqueue entries done.
471 Useful for test routines that need to do a
472 lot of indexing without having to wait for
475 In NoZebra model, this only marks zebraqueue
476 done - the records should already be indexed.
483 # mark zebraqueue done regardless of the indexing mode
484 my $dbh = C4::Context->dbh();
485 $dbh->do("UPDATE zebraqueue SET done = 1 WHERE done = 0");
487 return if C4::Context->preference('NoZebra');
489 my $directory = tempdir(CLEANUP => 1);
490 foreach my $record_type qw(biblio authority) {
491 mkdir "$directory/$record_type";
492 my $sth = $dbh->prepare($record_type eq "biblio" ? "SELECT marc FROM biblioitems" : "SELECT marc FROM auth_header");
494 open OUT, ">:utf8", "$directory/$record_type/records";
495 while (my ($blob) = $sth->fetchrow_array) {
499 my $zebra_server = "${record_type}server";
500 my $zebra_config = C4::Context->zebraconfig($zebra_server)->{'config'};
501 my $zebra_db_dir = C4::Context->zebraconfig($zebra_server)->{'directory'};
502 my $zebra_db = $record_type eq 'biblio' ? 'biblios' : 'authorities';
503 system "zebraidx -c $zebra_config -d $zebra_db -g iso2709 init > /dev/null 2>\&1";
504 system "zebraidx -c $zebra_config -d $zebra_db -g iso2709 update $directory/${record_type} > /dev/null 2>\&1";
505 system "zebraidx -c $zebra_config -d $zebra_db -g iso2709 commit > /dev/null 2>\&1";
511 =head3 clear_test_database
513 removes all tables from test database so that install starts with a clean slate
517 sub clear_test_database {
519 diag "removing tables from test database";
521 my $dbh = C4::Context->dbh;
522 my $schema = C4::Context->config("database");
524 my @tables = get_all_tables($dbh, $schema);
525 foreach my $table (@tables) {
526 drop_all_foreign_keys($dbh, $table);
529 foreach my $table (@tables) {
530 drop_table($dbh, $table);
535 my ($dbh, $schema) = @_;
536 my $sth = $dbh->prepare("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?");
538 $sth->execute($schema);
539 while (my ($table) = $sth->fetchrow_array) {
540 push @tables, $table;
546 sub drop_all_foreign_keys {
547 my ($dbh, $table) = @_;
548 # get the table description
549 my $sth = $dbh->prepare("SHOW CREATE TABLE $table");
551 my $vsc_structure = $sth->fetchrow;
552 # split on CONSTRAINT keyword
553 my @fks = split /CONSTRAINT /,$vsc_structure;
556 # isolate what is before FOREIGN KEY, if there is something, it's a foreign key to drop
557 $_ = /(.*) FOREIGN KEY.*/;
560 # we have found 1 foreign, drop it
561 $dbh->do("ALTER TABLE $table DROP FOREIGN KEY $id");
568 my ($dbh, $table) = @_;
569 $dbh->do("DROP TABLE $table");
572 =head3 create_test_database
574 sets up the test database.
578 sub create_test_database {
580 diag 'creating testing database...';
581 my $installer = C4::Installer->new() or die 'unable to create new installer';
582 # warn Data::Dumper->Dump( [ $installer ], [ 'installer' ] );
583 my $all_languages = getAllLanguages();
584 my $error = $installer->load_db_schema();
585 die "unable to load_db_schema: $error" if ( $error );
586 my $list = $installer->sql_file_list('en', 'marc21', { optional => 1,
588 my ($fwk_language, $installed_list) = $installer->load_sql_in_order($all_languages, @$list);
589 $installer->set_version_syspref();
590 $installer->set_marcflavour_syspref('MARC21');
591 $installer->set_indexing_engine(0);
592 diag 'database created.'
596 =head3 start_zebrasrv
598 This method deletes and reinitializes the zebra database directory,
599 and then spans off a zebra server.
606 diag 'cleaning zebrasrv...';
608 foreach my $zebra_server ( qw( biblioserver authorityserver ) ) {
609 my $zebra_config = C4::Context->zebraconfig($zebra_server)->{'config'};
610 my $zebra_db_dir = C4::Context->zebraconfig($zebra_server)->{'directory'};
611 foreach my $zebra_db_name ( qw( biblios authorities ) ) {
612 my $command = "zebraidx -c $zebra_config -d $zebra_db_name init";
613 my $return = system( $command . ' > /dev/null 2>&1' );
614 if ( $return != 0 ) {
615 diag( "command '$command' died with value: " . $? >> 8 );
618 $command = "zebraidx -c $zebra_config -d $zebra_db_name create $zebra_db_name";
620 $return = system( $command . ' > /dev/null 2>&1' );
621 if ( $return != 0 ) {
622 diag( "command '$command' died with value: " . $? >> 8 );
627 diag 'starting zebrasrv...';
629 my $pidfile = File::Spec->catdir( C4::Context->config("logdir"), 'zebra.pid' );
630 my $command = sprintf( 'zebrasrv -f %s -D -l %s -p %s',
632 File::Spec->catdir( C4::Context->config("logdir"), 'zebra.log' ),
636 my $output = qx( $command );
640 if ( -e $pidfile, 'pidfile exists' ) {
641 diag 'zebrasrv started.';
643 die 'unable to start zebrasrv';
650 using the PID file for the zebra server, send it a TERM signal with
651 "kill". We can't tell if the process actually dies or not.
657 my $pidfile = File::Spec->catdir( C4::Context->config("logdir"), 'zebra.pid' );
659 open( my $pidh, '<', $pidfile )
661 if ( defined $pidh ) {
662 my ( $pid ) = <$pidh> or return;
664 my $killed = kill 15, $pid; # 15 is TERM
665 if ( $killed != 1 ) {
666 warn "unable to kill zebrasrv with pid: $pid";
673 =head3 start_zebraqueue_daemon
675 kick off a zebraqueue_daemon.pl process.
679 sub start_zebraqueue_daemon {
681 my $command = q(run/bin/koha-zebraqueue-ctl.sh start);
683 my $started = system( $command );
684 diag "started: $started";
688 =head3 stop_zebraqueue_daemon
693 sub stop_zebraqueue_daemon {
695 my $command = q(run/bin/koha-zebraqueue-ctl.sh stop);
697 my $started = system( $command );
698 diag "started: $started";