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 /;
25 # Since this is an abstract base class, this prevents these tests from
26 # being run directly unless we're testing a subclass. It just makes
28 __PACKAGE__->SKIP_CLASS( 1 );
31 if ($ENV{SINGLE_TEST}) {
32 # if we're running the tests in one
33 # or more test files specified via
35 # make test-single TEST_FILES=lib/KohaTest/Foo.pm
37 # use this INIT trick taken from the POD for
40 Test::Class->runtests;
45 use Attribute::Handlers;
47 =head2 Expensive test method attribute
49 If a test method is decorated with an Expensive
50 attribute, it is skipped unless the RUN_EXPENSIVE_TESTS
51 environment variable is defined.
53 To declare an entire test class and its subclasses expensive,
54 define a SKIP_CLASS with the Expensive attribute:
56 sub SKIP_CLASS : Expensive { }
60 sub Expensive : ATTR(CODE) {
61 my ($package, $symbol, $sub, $attr, $data, $phase) = @_;
62 my $name = *{$symbol}{NAME};
63 if ($name eq 'SKIP_CLASS') {
64 if ($ENV{'RUN_EXPENSIVE_TESTS'}) {
65 *{$symbol} = sub { 0; }
67 *{$symbol} = sub { "Skipping expensive test classes $package (and subclasses)"; }
70 unless ($ENV{'RUN_EXPENSIVE_TESTS'}) {
71 # a test method that runs no tests and just returns a scalar is viewed by Test::Class as a skip
72 *{$symbol} = sub { "Skipping expensive test $package\:\:$name"; }
77 =head2 startup methods
79 these are run once, at the beginning of the whole test suite
83 sub startup_15_truncate_tables : Test( startup => 1 ) {
86 my @truncate_tables = qw( accountlines
134 subscriptionroutinglist
140 my $failed_to_truncate = 0;
141 foreach my $table ( @truncate_tables ) {
142 my $dbh = C4::Context->dbh();
143 $dbh->do( "truncate $table" )
144 or $failed_to_truncate = 1;
146 is( $failed_to_truncate, 0, 'truncated tables' );
149 =head2 startup_20_add_bookseller
151 we need a bookseller for many of the tests, so let's insert one. Feel
152 free to use this one, or insert your own.
156 sub startup_20_add_bookseller : Test(startup => 1) {
159 my $booksellerinfo = { name => 'bookseller ' . $self->random_string(),
162 my $id = AddBookseller( $booksellerinfo );
163 ok( $id, "created bookseller: $id" );
164 $self->{'booksellerid'} = $id;
169 =head2 startup_22_add_bookfund
171 we need a bookfund for many of the tests. This currently uses one that
172 is in the skeleton database. free to use this one, or insert your
177 sub startup_22_add_bookfund : Test(startup => 2) {
180 my $bookfundid = 'GEN';
181 my $bookfund = GetBookFund( $bookfundid, undef );
182 # diag( Data::Dumper->Dump( [ $bookfund ], qw( bookfund ) ) );
183 is( $bookfund->{'bookfundid'}, $bookfundid, "found bookfund: '$bookfundid'" );
184 is( $bookfund->{'bookfundname'}, 'General Stacks', "found bookfund: '$bookfundid'" );
186 $self->{'bookfundid'} = $bookfundid;
190 =head2 startup_24_add_branch
194 sub startup_24_add_branch : Test(startup => 1) {
199 branchcode => $self->random_string(3),
200 branchname => $self->random_string(),
201 branchaddress1 => $self->random_string(),
202 branchaddress2 => $self->random_string(),
203 branchaddress3 => $self->random_string(),
204 branchphone => $self->random_phone(),
205 branchfax => $self->random_phone(),
206 brancemail => $self->random_email(),
207 branchip => $self->random_ip(),
208 branchprinter => $self->random_string(),
210 C4::Branch::ModBranch($branch_info);
211 $self->{'branchcode'} = $branch_info->{'branchcode'};
212 ok( $self->{'branchcode'}, "created branch: $self->{'branchcode'}" );
216 =head2 startup_24_add_member
218 Add a patron/member for the tests to use
222 sub startup_24_add_member : Test(startup => 1) {
225 my $memberinfo = { surname => 'surname ' . $self->random_string(),
226 firstname => 'firstname' . $self->random_string(),
227 address => 'address' . $self->random_string(),
228 city => 'city' . $self->random_string(),
229 cardnumber => 'card' . $self->random_string(),
230 branchcode => 'CPL', # CPL => Centerville
231 categorycode => 'PT', # PT => PaTron
232 dateexpiry => '2010-01-01',
233 password => 'testpassword',
234 dateofbirth => $self->random_date(),
237 my $borrowernumber = AddMember( %$memberinfo );
238 ok( $borrowernumber, "created member: $borrowernumber" );
239 $self->{'memberid'} = $borrowernumber;
244 =head2 startup_30_login
248 sub startup_30_login : Test( startup => 2 ) {
251 $self->{'sessionid'} = '12345678'; # does this value matter?
252 my $borrower_details = C4::Members::GetMemberDetails( $self->{'memberid'} );
253 ok( $borrower_details->{'cardnumber'}, 'cardnumber' );
255 # make a cookie and force it into $cgi.
256 # This would be a lot easier with Test::MockObject::Extends.
257 my $cgi = CGI->new( { userid => $borrower_details->{'cardnumber'},
258 password => 'testpassword' } );
259 my $setcookie = $cgi->cookie( -name => 'CGISESSID',
260 -value => $self->{'sessionid'} );
261 $cgi->{'.cookies'} = { CGISESSID => $setcookie };
262 is( $cgi->cookie('CGISESSID'), $self->{'sessionid'}, 'the CGISESSID cookie is set' );
263 # diag( Data::Dumper->Dump( [ $cgi->cookie('CGISESSID') ], [ qw( cookie ) ] ) );
265 # C4::Auth::checkauth sometimes emits a warning about unable to append to sessionlog. That's OK.
266 my ( $userid, $cookie, $sessionID ) = C4::Auth::checkauth( $cgi, 'noauth', {}, 'intranet' );
267 # diag( Data::Dumper->Dump( [ $userid, $cookie, $sessionID ], [ qw( userid cookie sessionID ) ] ) );
269 # my $session = C4::Auth::get_session( $sessionID );
270 # diag( Data::Dumper->Dump( [ $session ], [ qw( session ) ] ) );
277 setup methods are run before every test method
281 =head2 teardown methods
283 teardown methods are many time, once at the end of each test method.
287 =head2 shutdown methods
289 shutdown methods are run once, at the end of the test suite
293 =head2 utility methods
295 These are not test methods, but they're handy
301 Nice for generating names and such. It's not actually random, more
309 my $wordsize = shift || 6; # how many letters in your string?
311 # leave out these characters: "oOlL10". They're too confusing.
312 my @alphabet = ( 'a'..'k','m','n','p'..'z', 'A'..'K','M','N','P'..'Z', 2..9 );
315 foreach ( 0..$wordsize ) {
316 $randomstring .= $alphabet[ rand( scalar( @alphabet ) ) ];
318 return $randomstring;
324 generates a random phone number. Currently, it's not actually random. It's an unusable US phone number
331 return '212-555-5555';
337 generates a random email address. They're all in the unusable
338 'example.com' domain that is designed for this purpose.
345 return $self->random_string() . '@example.com';
351 returns an IP address suitable for testing purposes.
364 returns a somewhat random date in the iso (yyyy-mm-dd) format.
371 my $year = 1800 + int( rand(300) ); # 1800 - 2199
372 my $month = 1 + int( rand(12) ); # 1 - 12
373 my $day = 1 + int( rand(28) ); # 1 - 28
374 # stop at the 28th to keep us from generating February 31st and such.
376 return sprintf( '%04d-%02d-%02d', $year, $month, $day );
382 returns tomorrow's date as YYYY-MM-DD.
389 return $self->days_from_now( 1 );
395 returns yesterday's date as YYYY-MM-DD.
402 return $self->days_from_now( -1 );
408 returns an arbitrary date based on today in YYYY-MM-DD format.
414 my $days = shift or return;
416 my $seconds = time + $days * 60*60*24;
417 my $yyyymmdd = sprintf( '%04d-%02d-%02d',
418 localtime( $seconds )->year() + 1900,
419 localtime( $seconds )->mon() + 1,
420 localtime( $seconds )->mday() );
426 $self->add_biblios( count => 10,
430 count: number of biblios to add
431 add_items: should you add items for each one?
437 adds the biblionumbers to the $self->{'biblios'} listref
440 Should I allow you to pass in biblio information, like title?
441 Since this method is in the KohaTest class, all tests in it will be ignored, unless you call this from your own namespace.
442 This runs 10 tests, plus 4 for each "count", plus 3 more for each item added.
450 $param{'count'} = 1 unless defined( $param{'count'} );
451 $param{'add_items'} = 0 unless defined( $param{'add_items'} );
453 foreach my $counter ( 1..$param{'count'} ) {
454 my $marcrecord = MARC::Record->new();
455 isa_ok( $marcrecord, 'MARC::Record' );
456 my @marc_fields = ( MARC::Field->new( '100', '1', '0',
459 MARC::Field->new( '245', '1', '4',
460 a => sprintf( 'The Adventures of Huckleberry Finn Test %s', $counter ),
461 c => "Mark Twain ; illustrated by E.W. Kemble." ),
462 MARC::Field->new( '952', '0', '0',
463 p => '12345678' . $self->random_string() ), # barcode
464 MARC::Field->new( '952', '0', '0',
465 o => $self->random_string() ), # callnumber
466 MARC::Field->new( '952', '0', '0',
471 my $appendedfieldscount = $marcrecord->append_fields( @marc_fields );
473 diag $MARC::Record::ERROR if ( $MARC::Record::ERROR );
474 is( $appendedfieldscount, scalar @marc_fields, 'added correct number of MARC fields' );
476 my $frameworkcode = ''; # XXX I'd like to put something reasonable here.
477 my ( $biblionumber, $biblioitemnumber ) = AddBiblio( $marcrecord, $frameworkcode );
478 ok( $biblionumber, "the biblionumber is $biblionumber" );
479 ok( $biblioitemnumber, "the biblioitemnumber is $biblioitemnumber" );
480 if ( $param{'add_items'} ) {
481 # my @iteminfo = AddItem( {}, $biblionumber );
482 my @iteminfo = AddItemFromMarc( $marcrecord, $biblionumber );
483 is( $iteminfo[0], $biblionumber, "biblionumber is $biblionumber" );
484 is( $iteminfo[1], $biblioitemnumber, "biblioitemnumber is $biblioitemnumber" );
485 ok( $iteminfo[2], "itemnumber is $iteminfo[2]" );
486 push @{ $self->{'items'} },
487 { biblionumber => $iteminfo[0],
488 biblioitemnumber => $iteminfo[1],
489 itemnumber => $iteminfo[2],
492 push @{$self->{'biblios'}}, $biblionumber;
495 $self->reindex_marc();
496 my $query = 'Finn Test';
497 my ( $error, $results ) = SimpleSearch( $query );
498 if ( $param{'count'} <= scalar( @$results ) ) {
499 pass( "found all $param{'count'} titles" );
501 fail( "we never found all $param{'count'} titles" );
508 Do a fast reindexing of all of the bib and authority
509 records and mark all zebraqueue entries done.
511 Useful for test routines that need to do a
512 lot of indexing without having to wait for
515 In NoZebra model, this only marks zebraqueue
516 done - the records should already be indexed.
523 # mark zebraqueue done regardless of the indexing mode
524 my $dbh = C4::Context->dbh();
525 $dbh->do("UPDATE zebraqueue SET done = 1 WHERE done = 0");
527 return if C4::Context->preference('NoZebra');
529 my $directory = tempdir(CLEANUP => 1);
530 foreach my $record_type qw(biblio authority) {
531 mkdir "$directory/$record_type";
532 my $sth = $dbh->prepare($record_type eq "biblio" ? "SELECT marc FROM biblioitems" : "SELECT marc FROM auth_header");
534 open OUT, ">:utf8", "$directory/$record_type/records";
535 while (my ($blob) = $sth->fetchrow_array) {
539 my $zebra_server = "${record_type}server";
540 my $zebra_config = C4::Context->zebraconfig($zebra_server)->{'config'};
541 my $zebra_db_dir = C4::Context->zebraconfig($zebra_server)->{'directory'};
542 my $zebra_db = $record_type eq 'biblio' ? 'biblios' : 'authorities';
543 system "zebraidx -c $zebra_config -d $zebra_db -g iso2709 init > /dev/null 2>\&1";
544 system "zebraidx -c $zebra_config -d $zebra_db -g iso2709 update $directory/${record_type} > /dev/null 2>\&1";
545 system "zebraidx -c $zebra_config -d $zebra_db -g iso2709 commit > /dev/null 2>\&1";
551 =head3 clear_test_database
553 removes all tables from test database so that install starts with a clean slate
557 sub clear_test_database {
559 diag "removing tables from test database";
561 my $dbh = C4::Context->dbh;
562 my $schema = C4::Context->config("database");
564 my @tables = get_all_tables($dbh, $schema);
565 foreach my $table (@tables) {
566 drop_all_foreign_keys($dbh, $table);
569 foreach my $table (@tables) {
570 drop_table($dbh, $table);
575 my ($dbh, $schema) = @_;
576 my $sth = $dbh->prepare("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?");
578 $sth->execute($schema);
579 while (my ($table) = $sth->fetchrow_array) {
580 push @tables, $table;
586 sub drop_all_foreign_keys {
587 my ($dbh, $table) = @_;
588 # get the table description
589 my $sth = $dbh->prepare("SHOW CREATE TABLE $table");
591 my $vsc_structure = $sth->fetchrow;
592 # split on CONSTRAINT keyword
593 my @fks = split /CONSTRAINT /,$vsc_structure;
596 # isolate what is before FOREIGN KEY, if there is something, it's a foreign key to drop
597 $_ = /(.*) FOREIGN KEY.*/;
600 # we have found 1 foreign, drop it
601 $dbh->do("ALTER TABLE $table DROP FOREIGN KEY $id");
603 diag "unable to DROP FOREIGN KEY '$id' on TABLE '$table' due to: " . $dbh->errstr();
611 my ($dbh, $table) = @_;
612 $dbh->do("DROP TABLE $table");
614 diag "unable to drop table: '$table' due to: " . $dbh->errstr();
618 =head3 create_test_database
620 sets up the test database.
624 sub create_test_database {
626 diag 'creating testing database...';
627 my $installer = C4::Installer->new() or die 'unable to create new installer';
628 # warn Data::Dumper->Dump( [ $installer ], [ 'installer' ] );
629 my $all_languages = getAllLanguages();
630 my $error = $installer->load_db_schema();
631 die "unable to load_db_schema: $error" if ( $error );
632 my $list = $installer->sql_file_list('en', 'marc21', { optional => 1,
634 my ($fwk_language, $installed_list) = $installer->load_sql_in_order($all_languages, @$list);
635 $installer->set_version_syspref();
636 $installer->set_marcflavour_syspref('MARC21');
637 $installer->set_indexing_engine(0);
638 diag 'database created.'
642 =head3 start_zebrasrv
644 This method deletes and reinitializes the zebra database directory,
645 and then spans off a zebra server.
652 diag 'cleaning zebrasrv...';
654 foreach my $zebra_server ( qw( biblioserver authorityserver ) ) {
655 my $zebra_config = C4::Context->zebraconfig($zebra_server)->{'config'};
656 my $zebra_db_dir = C4::Context->zebraconfig($zebra_server)->{'directory'};
657 foreach my $zebra_db_name ( qw( biblios authorities ) ) {
658 my $command = "zebraidx -c $zebra_config -d $zebra_db_name init";
659 my $return = system( $command . ' > /dev/null 2>&1' );
660 if ( $return != 0 ) {
661 diag( "command '$command' died with value: " . $? >> 8 );
664 $command = "zebraidx -c $zebra_config -d $zebra_db_name create $zebra_db_name";
666 $return = system( $command . ' > /dev/null 2>&1' );
667 if ( $return != 0 ) {
668 diag( "command '$command' died with value: " . $? >> 8 );
673 diag 'starting zebrasrv...';
675 my $pidfile = File::Spec->catdir( C4::Context->config("logdir"), 'zebra.pid' );
676 my $command = sprintf( 'zebrasrv -f %s -D -l %s -p %s',
678 File::Spec->catdir( C4::Context->config("logdir"), 'zebra.log' ),
682 my $output = qx( $command );
686 if ( -e $pidfile, 'pidfile exists' ) {
687 diag 'zebrasrv started.';
689 die 'unable to start zebrasrv';
696 using the PID file for the zebra server, send it a TERM signal with
697 "kill". We can't tell if the process actually dies or not.
703 my $pidfile = File::Spec->catdir( C4::Context->config("logdir"), 'zebra.pid' );
705 open( my $pidh, '<', $pidfile )
707 if ( defined $pidh ) {
708 my ( $pid ) = <$pidh> or return;
710 my $killed = kill 15, $pid; # 15 is TERM
711 if ( $killed != 1 ) {
712 warn "unable to kill zebrasrv with pid: $pid";
719 =head3 start_zebraqueue_daemon
721 kick off a zebraqueue_daemon.pl process.
725 sub start_zebraqueue_daemon {
727 my $command = q(run/bin/koha-zebraqueue-ctl.sh start);
729 my $started = system( $command );
730 diag "started: $started";
734 =head3 stop_zebraqueue_daemon
739 sub stop_zebraqueue_daemon {
741 my $command = q(run/bin/koha-zebraqueue-ctl.sh stop);
743 my $started = system( $command );
744 diag "started: $started";