diff options
Diffstat (limited to 'rt/lib/RT/Test')
| -rw-r--r-- | rt/lib/RT/Test/Apache.pm | 270 | ||||
| -rw-r--r-- | rt/lib/RT/Test/Email.pm | 7 | ||||
| -rw-r--r-- | rt/lib/RT/Test/GnuPG.pm | 360 | ||||
| -rw-r--r-- | rt/lib/RT/Test/Web.pm | 203 |
4 files changed, 819 insertions, 21 deletions
diff --git a/rt/lib/RT/Test/Apache.pm b/rt/lib/RT/Test/Apache.pm new file mode 100644 index 000000000..b2733eadb --- /dev/null +++ b/rt/lib/RT/Test/Apache.pm @@ -0,0 +1,270 @@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC +# <sales@bestpractical.com> +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: +# +# This work is made available to you under the terms of Version 2 of +# the GNU General Public License. A copy of that license should have +# been provided with this software, but in any event can be snarfed +# from www.gnu.org. +# +# This work 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +# +# +# CONTRIBUTION SUBMISSION POLICY: +# +# (The following paragraph is not intended to limit the rights granted +# to you to modify and distribute this software under the terms of +# the GNU General Public License and is only of importance to you if +# you choose to contribute your changes and enhancements to the +# community by submitting them to Best Practical Solutions, LLC.) +# +# By intentionally submitting any modifications, corrections or +# derivatives to this work, or any other work intended for use with +# Request Tracker, to Best Practical Solutions, LLC, you confirm that +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# royalty-free, perpetual, license to use, copy, create derivative +# works based on those contributions, and sublicense and distribute +# those contributions and any derivatives thereof. +# +# END BPS TAGGED BLOCK }}} + +package RT::Test::Apache; +use strict; +use warnings; + +my %MODULES = ( + '2.2' => { + "mod_perl" => [qw(authz_host env alias perl)], + "fastcgi" => [qw(authz_host env alias mime fastcgi)], + }, +); + +my $apache_module_prefix = $ENV{RT_TEST_APACHE_MODULES}; +my $apxs = + $ENV{RT_TEST_APXS} + || RT::Test->find_executable('apxs') + || RT::Test->find_executable('apxs2'); + +if ($apxs and not $apache_module_prefix) { + $apache_module_prefix = `$apxs -q LIBEXECDIR`; + chomp $apache_module_prefix; +} + +$apache_module_prefix ||= 'modules'; + +sub basic_auth { + my $self = shift; + my $passwd = File::Spec->rel2abs( File::Spec->catfile( + 't', 'data', 'configs', 'passwords' ) ); + + return <<"EOT"; + AuthType Basic + AuthName "restricted area" + AuthUserFile $passwd + Require user root +EOT +} + +sub start_server { + my ($self, %config) = @_; + my %tmp = %{$config{tmp}}; + my %info = $self->apache_server_info( %config ); + + RT::Test::diag(do { + open( my $fh, '<', $tmp{'config'}{'RT'} ) or die $!; + local $/; + <$fh> + }); + + my $tmpl = File::Spec->rel2abs( File::Spec->catfile( + 't', 'data', 'configs', + 'apache'. $info{'version'} .'+'. $config{variant} .'.conf' + ) ); + my %opt = ( + listen => $config{port}, + server_root => $info{'HTTPD_ROOT'} || $ENV{'HTTPD_ROOT'} + || Test::More::BAIL_OUT("Couldn't figure out server root"), + document_root => $RT::MasonComponentRoot, + tmp_dir => "$tmp{'directory'}", + rt_bin_path => $RT::BinPath, + rt_sbin_path => $RT::SbinPath, + rt_site_config => $ENV{'RT_SITE_CONFIG'}, + load_modules => $info{load_modules}, + basic_auth => $config{basic_auth} ? $self->basic_auth : "", + ); + foreach (qw(log pid lock)) { + $opt{$_ .'_file'} = File::Spec->catfile( + "$tmp{'directory'}", "apache.$_" + ); + } + + $tmp{'config'}{'apache'} = File::Spec->catfile( + "$tmp{'directory'}", "apache.conf" + ); + $self->process_in_file( + in => $tmpl, + out => $tmp{'config'}{'apache'}, + options => \%opt, + ); + + $self->fork_exec($info{'executable'}, '-f', $tmp{'config'}{'apache'}); + my $pid = do { + my $tries = 15; + while ( !-s $opt{'pid_file'} ) { + $tries--; + last unless $tries; + sleep 1; + } + my $pid_fh; + unless (-e $opt{'pid_file'} and open($pid_fh, '<', $opt{'pid_file'})) { + Test::More::BAIL_OUT("Couldn't start apache server, no pid file (unknown error)") + unless -e $opt{log_file}; + + open my $log, "<", $opt{log_file}; + my $error = do {local $/; <$log>}; + close $log; + $RT::Logger->error($error) if $error; + Test::More::BAIL_OUT("Couldn't start apache server!"); + } + + my $pid = <$pid_fh>; + chomp $pid; + $pid; + }; + + Test::More::ok($pid, "Started apache server #$pid"); + return $pid; +} + +sub apache_server_info { + my $self = shift; + my %res = @_; + + my $bin = $res{'executable'} = $ENV{'RT_TEST_APACHE'} + || $self->find_apache_server + || Test::More::BAIL_OUT("Couldn't find apache server, use RT_TEST_APACHE"); + + Test::More::BAIL_OUT( + "Couldn't find apache modules directory (set APXS= or RT_TEST_APACHE_MODULES=)" + ) unless -d $apache_module_prefix; + + + RT::Test::diag("Using '$bin' apache executable for testing"); + + my $info = `$bin -V`; + ($res{'version'}) = ($info =~ m{Server\s+version:\s+Apache/(\d+\.\d+)\.}); + Test::More::BAIL_OUT( + "Couldn't figure out version of the server" + ) unless $res{'version'}; + + my %opts = ($info =~ m/^\s*-D\s+([A-Z_]+?)(?:="(.*)")$/mg); + %res = (%res, %opts); + + $res{'modules'} = [ + map {s/^\s+//; s/\s+$//; $_} + grep $_ !~ /Compiled in modules/i, + split /\r*\n/, `$bin -l` + ]; + + Test::More::BAIL_OUT( + "Unsupported apache version $res{version}" + ) unless exists $MODULES{$res{version}}; + + Test::More::BAIL_OUT( + "Unsupported apache variant $res{variant}" + ) unless exists $MODULES{$res{version}}{$res{variant}}; + + my @mlist = @{$MODULES{$res{version}}{$res{variant}}}; + push @mlist, "authn_file", "auth_basic", "authz_user" if $res{basic_auth}; + + $res{'load_modules'} = ''; + foreach my $mod ( @mlist ) { + next if grep $_ =~ /^(mod_|)$mod\.c$/, @{ $res{'modules'} }; + + my $so_file = $apache_module_prefix."/mod_".$mod.".so"; + Test::More::BAIL_OUT( "Couldn't load $mod module (expected in $so_file)" ) + unless -f $so_file; + $res{'load_modules'} .= + "LoadModule ${mod}_module $so_file\n"; + } + return %res; +} + +sub find_apache_server { + my $self = shift; + return $_ foreach grep defined, + map RT::Test->find_executable($_), + qw(httpd apache apache2 apache1); + return undef; +} + +sub apache_mpm_type { + my $self = shift; + my $apache = $self->find_apache_server; + my $out = `$apache -l`; + if ( $out =~ /^\s*(worker|prefork|event|itk)\.c\s*$/m ) { + return $1; + } +} + +sub fork_exec { + my $self = shift; + + RT::Test::__disconnect_rt(); + my $pid = fork; + unless ( defined $pid ) { + die "cannot fork: $!"; + } elsif ( !$pid ) { + exec @_; + die "can't exec `". join(' ', @_) ."` program: $!"; + } else { + RT::Test::__reconnect_rt(); + return $pid; + } +} + +sub process_in_file { + my $self = shift; + my %args = ( in => undef, options => undef, @_ ); + + my $text = RT::Test->file_content( $args{'in'} ); + while ( my ($opt) = ($text =~ /\%\%(.+?)\%\%/) ) { + my $value = $args{'options'}{ lc $opt }; + die "no value for $opt" unless defined $value; + + $text =~ s/\%\%\Q$opt\E\%\%/$value/g; + } + + my ($out_fh, $out_conf); + unless ( $args{'out'} ) { + ($out_fh, $out_conf) = tempfile(); + } else { + $out_conf = $args{'out'}; + open( $out_fh, '>', $out_conf ) + or die "couldn't open '$out_conf': $!"; + } + print $out_fh $text; + seek $out_fh, 0, 0; + + return ($out_fh, $out_conf); +} + +1; diff --git a/rt/lib/RT/Test/Email.pm b/rt/lib/RT/Test/Email.pm index 3905d82cb..a8170c981 100644 --- a/rt/lib/RT/Test/Email.pm +++ b/rt/lib/RT/Test/Email.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -56,8 +56,6 @@ use Email::Abstract; use base 'Exporter'; our @EXPORT = qw(mail_ok); -RT::Test->set_mail_catcher; - =head1 NAME RT::Test::Email - @@ -122,7 +120,8 @@ END { if (scalar @mail) { diag ((scalar @mail)." uncaught notification email at end of test: "); diag "From: @{[ $_->header('From' ) ]}, Subject: @{[ $_->header('Subject') ]}" - for @mail; + for map { Email::Abstract->new($_)->cast('Email::Simple') } @mail; + die; } } diff --git a/rt/lib/RT/Test/GnuPG.pm b/rt/lib/RT/Test/GnuPG.pm new file mode 100644 index 000000000..6cebb775b --- /dev/null +++ b/rt/lib/RT/Test/GnuPG.pm @@ -0,0 +1,360 @@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC +# <sales@bestpractical.com> +# +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: +# +# This work is made available to you under the terms of Version 2 of +# the GNU General Public License. A copy of that license should have +# been provided with this software, but in any event can be snarfed +# from www.gnu.org. +# +# This work 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. +# +# +# CONTRIBUTION SUBMISSION POLICY: +# +# (The following paragraph is not intended to limit the rights granted +# to you to modify and distribute this software under the terms of +# the GNU General Public License and is only of importance to you if +# you choose to contribute your changes and enhancements to the +# community by submitting them to Best Practical Solutions, LLC.) +# +# By intentionally submitting any modifications, corrections or +# derivatives to this work, or any other work intended for use with +# Request Tracker, to Best Practical Solutions, LLC, you confirm that +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# royalty-free, perpetual, license to use, copy, create derivative +# works based on those contributions, and sublicense and distribute +# those contributions and any derivatives thereof. +# +# END BPS TAGGED BLOCK }}} + +package RT::Test::GnuPG; +use strict; +use Test::More; +use base qw(RT::Test); +use File::Temp qw(tempdir); + +our @EXPORT = + qw(create_a_ticket update_ticket cleanup_headers set_queue_crypt_options + check_text_emails send_email_and_check_transaction + create_and_test_outgoing_emails + ); + +sub import { + my $class = shift; + my %args = @_; + my $t = $class->builder; + + $t->plan( skip_all => 'GnuPG required.' ) + unless eval { require GnuPG::Interface; 1 }; + $t->plan( skip_all => 'gpg executable is required.' ) + unless RT::Test->find_executable('gpg'); + + require RT::Crypt::GnuPG; + $class->SUPER::import(%args); + + RT::Test::diag "GnuPG --homedir " . RT->Config->Get('GnuPGOptions')->{'homedir'}; + + $class->set_rights( + Principal => 'Everyone', + Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'], + ); + + $class->export_to_level(1); +} + +sub bootstrap_more_config { + my $self = shift; + my $handle = shift; + my $args = shift; + + $self->SUPER::bootstrap_more_config($handle, $args, @_); + + my %gnupg_options = ( + 'no-permission-warning' => undef, + $args->{gnupg_options} ? %{ $args->{gnupg_options} } : (), + ); + $gnupg_options{homedir} ||= scalar tempdir( CLEANUP => 1 ); + + use Data::Dumper; + local $Data::Dumper::Terse = 1; # "{...}" instead of "$VAR1 = {...};" + my $dumped_gnupg_options = Dumper(\%gnupg_options); + + print $handle qq{ +Set(\%GnuPG, ( + Enable => 1, + OutgoingMessagesFormat => 'RFC', +)); +Set(\%GnuPGOptions => \%{ $dumped_gnupg_options }); +Set(\@MailPlugins => qw(Auth::MailFrom Auth::GnuPG)); +}; + +} + +sub create_a_ticket { + my $queue = shift; + my $mail = shift; + my $m = shift; + my %args = (@_); + + RT::Test->clean_caught_mails; + + $m->goto_create_ticket( $queue ); + $m->form_name('TicketCreate'); + $m->field( Subject => 'test' ); + $m->field( Requestors => 'rt-test@example.com' ); + $m->field( Content => 'Some content' ); + + foreach ( qw(Sign Encrypt) ) { + if ( $args{ $_ } ) { + $m->tick( $_ => 1 ); + } else { + $m->untick( $_ => 1 ); + } + } + + $m->submit; + is $m->status, 200, "request successful"; + + $m->content_lacks("unable to sign outgoing email messages"); + + + my @mail = RT::Test->fetch_caught_mails; + check_text_emails(\%args, @mail ); + categorize_emails($mail, \%args, @mail ); +} + +sub update_ticket { + my $tid = shift; + my $mail = shift; + my $m = shift; + my %args = (@_); + + RT::Test->clean_caught_mails; + + $m->get( $m->rt_base_url . "/Ticket/Update.html?Action=Respond&id=$tid" ); + $m->form_number(3); + $m->field( UpdateContent => 'Some content' ); + + foreach ( qw(Sign Encrypt) ) { + if ( $args{ $_ } ) { + $m->tick( $_ => 1 ); + } else { + $m->untick( $_ => 1 ); + } + } + + $m->click('SubmitTicket'); + is $m->status, 200, "request successful"; + $m->content_contains("Message recorded", 'Message recorded') or diag $m->content; + + + my @mail = RT::Test->fetch_caught_mails; + check_text_emails(\%args, @mail ); + categorize_emails($mail, \%args, @mail ); +} + +sub categorize_emails { + my $mail = shift; + my $args = shift; + my @mail = @_; + + if ( $args->{'Sign'} && $args->{'Encrypt'} ) { + push @{ $mail->{'signed_encrypted'} }, @mail; + } + elsif ( $args->{'Sign'} ) { + push @{ $mail->{'signed'} }, @mail; + } + elsif ( $args->{'Encrypt'} ) { + push @{ $mail->{'encrypted'} }, @mail; + } + else { + push @{ $mail->{'plain'} }, @mail; + } +} + +sub check_text_emails { + my %args = %{ shift @_ }; + my @mail = @_; + + ok scalar @mail, "got some mail"; + for my $mail (@mail) { + for my $type ('email', 'attachment') { + next if $type eq 'attachment' && !$args{'Attachment'}; + + my $content = $type eq 'email' + ? "Some content" + : "Attachment content"; + + if ( $args{'Encrypt'} ) { + unlike $mail, qr/$content/, "outgoing $type was encrypted"; + } else { + like $mail, qr/$content/, "outgoing $type was not encrypted"; + } + + next unless $type eq 'email'; + + if ( $args{'Sign'} && $args{'Encrypt'} ) { + like $mail, qr/BEGIN PGP MESSAGE/, 'outgoing email was signed'; + } elsif ( $args{'Sign'} ) { + like $mail, qr/SIGNATURE/, 'outgoing email was signed'; + } else { + unlike $mail, qr/SIGNATURE/, 'outgoing email was not signed'; + } + } + } +} + +sub cleanup_headers { + my $mail = shift; + # strip id from subject to create new ticket + $mail =~ s/^(Subject:)\s*\[.*?\s+#\d+\]\s*/$1 /m; + # strip several headers + foreach my $field ( qw(Message-ID X-RT-Original-Encoding RT-Originator RT-Ticket X-RT-Loop-Prevention) ) { + $mail =~ s/^$field:.*?\n(?! |\t)//gmsi; + } + return $mail; +} + +sub set_queue_crypt_options { + my $queue = shift; + my %args = @_; + $queue->SetEncrypt($args{'Encrypt'}); + $queue->SetSign($args{'Sign'}); +} + +sub send_email_and_check_transaction { + my $mail = shift; + my $type = shift; + + my ( $status, $id ) = RT::Test->send_via_mailgate($mail); + is( $status >> 8, 0, "The mail gateway exited normally" ); + ok( $id, "got id of a newly created ticket - $id" ); + + my $tick = RT::Ticket->new( RT->SystemUser ); + $tick->Load($id); + ok( $tick->id, "loaded ticket #$id" ); + + my $txn = $tick->Transactions->First; + my ( $msg, @attachments ) = @{ $txn->Attachments->ItemsArrayRef }; + + if ( $attachments[0] ) { + like $attachments[0]->Content, qr/Some content/, + "RT's mail includes copy of ticket text"; + } + else { + like $msg->Content, qr/Some content/, + "RT's mail includes copy of ticket text"; + } + + if ( $type eq 'plain' ) { + ok !$msg->GetHeader('X-RT-Privacy'), "RT's outgoing mail has no crypto"; + is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted', + "RT's outgoing mail looks not encrypted"; + ok !$msg->GetHeader('X-RT-Incoming-Signature'), + "RT's outgoing mail looks not signed"; + } + elsif ( $type eq 'signed' ) { + is $msg->GetHeader('X-RT-Privacy'), 'PGP', + "RT's outgoing mail has crypto"; + is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted', + "RT's outgoing mail looks not encrypted"; + like $msg->GetHeader('X-RT-Incoming-Signature'), + qr/<rt-recipient\@example.com>/, + "RT's outgoing mail looks signed"; + } + elsif ( $type eq 'encrypted' ) { + is $msg->GetHeader('X-RT-Privacy'), 'PGP', + "RT's outgoing mail has crypto"; + is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success', + "RT's outgoing mail looks encrypted"; + ok !$msg->GetHeader('X-RT-Incoming-Signature'), + "RT's outgoing mail looks not signed"; + + } + elsif ( $type eq 'signed_encrypted' ) { + is $msg->GetHeader('X-RT-Privacy'), 'PGP', + "RT's outgoing mail has crypto"; + is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success', + "RT's outgoing mail looks encrypted"; + like $msg->GetHeader('X-RT-Incoming-Signature'), + qr/<rt-recipient\@example.com>/, + "RT's outgoing mail looks signed"; + } + else { + die "unknown type: $type"; + } +} + +sub create_and_test_outgoing_emails { + my $queue = shift; + my $m = shift; + my @variants = + ( {}, { Sign => 1 }, { Encrypt => 1 }, { Sign => 1, Encrypt => 1 }, ); + + # collect emails + my %mail; + + # create a ticket for each combination + foreach my $ticket_set (@variants) { + create_a_ticket( $queue, \%mail, $m, %$ticket_set ); + } + + my $tid; + { + my $ticket = RT::Ticket->new( RT->SystemUser ); + ($tid) = $ticket->Create( + Subject => 'test', + Queue => $queue->id, + Requestor => 'rt-test@example.com', + ); + ok $tid, 'ticket created'; + } + + # again for each combination add a reply message + foreach my $ticket_set (@variants) { + update_ticket( $tid, \%mail, $m, %$ticket_set ); + } + +# ------------------------------------------------------------------------------ +# now delete all keys from the keyring and put back secret/pub pair for rt-test@ +# and only public key for rt-recipient@ so we can verify signatures and decrypt +# like we are on another side recieve emails +# ------------------------------------------------------------------------------ + + unlink $_ + foreach glob( RT->Config->Get('GnuPGOptions')->{'homedir'} . "/*" ); + RT::Test->import_gnupg_key( 'rt-recipient@example.com', 'public' ); + RT::Test->import_gnupg_key('rt-test@example.com'); + + $queue = RT::Test->load_or_create_queue( + Name => 'Regression', + CorrespondAddress => 'rt-test@example.com', + CommentAddress => 'rt-test@example.com', + ); + ok $queue && $queue->id, 'changed props of the queue'; + + for my $type ( keys %mail ) { + for my $mail ( map cleanup_headers($_), @{ $mail{$type} } ) { + send_email_and_check_transaction( $mail, $type ); + } + } +} diff --git a/rt/lib/RT/Test/Web.pm b/rt/lib/RT/Test/Web.pm index 62432d333..c2d9ac314 100644 --- a/rt/lib/RT/Test/Web.pm +++ b/rt/lib/RT/Test/Web.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC # <sales@bestpractical.com> # # (Except where explicitly superseded by other copyright notices) @@ -52,10 +52,24 @@ use strict; use warnings; use base qw(Test::WWW::Mechanize); +use Scalar::Util qw(weaken); -require RT::Test; +BEGIN { require RT::Test; } require Test::More; +my $instance; + +sub new { + my ($class, @args) = @_; + + push @args, app => $RT::Test::TEST_APP if $RT::Test::TEST_APP; + my $self = $instance = $class->SUPER::new(@args); + weaken $instance; + $self->cookie_jar(HTTP::Cookies->new); + + return $self; +} + sub get_ok { my $self = shift; my $url = shift; @@ -76,8 +90,9 @@ sub login { my $self = shift; my $user = shift || 'root'; my $pass = shift || 'password'; + my %args = @_; - $self->logout; + $self->logout if $args{logout}; my $url = $self->rt_base_url; $self->get($url . "?user=$user;pass=$pass"); @@ -85,11 +100,12 @@ sub login { Test::More::diag( "error: status is ". $self->status ); return 0; } - unless ( $self->content =~ qr/Logout/i ) { + unless ( $self->content =~ m/Logout/i ) { Test::More::diag("error: page has no Logout"); return 0; } - unless ( $self->content =~ m{<span>\Q$user\E</span>}i ) { + RT::Interface::Web::EscapeUTF8(\$user); + unless ( $self->content =~ m{<span class="current-user">\Q$user\E</span>}i ) { Test::More::diag("Page has no user name"); return 0; } @@ -104,7 +120,7 @@ sub logout { Test::More::diag( "error: status is ". $self->status ) unless $self->status == 200; - if ( $self->content =~ qr/Logout/i ) { + if ( $self->content =~ /Logout/i ) { $self->follow_link( text => 'Logout' ); Test::More::diag( "error: status is ". $self->status ." when tried to logout" ) unless $self->status == 200; @@ -114,7 +130,7 @@ sub logout { } $self->get($url); - if ( $self->content =~ qr/Logout/i ) { + if ( $self->content =~ /Logout/i ) { Test::More::diag( "error: couldn't logout" ); return 0; } @@ -130,7 +146,7 @@ sub goto_ticket { } my $url = $self->rt_base_url; - $url .= "/Ticket/Display.html?id=$id"; + $url .= "Ticket/Display.html?id=$id"; $self->get($url); unless ( $self->status == 200 ) { Test::More::diag( "error: status is ". $self->status ); @@ -152,24 +168,26 @@ sub goto_create_ticket { die "not yet implemented"; } - $self->get('/'); - $self->form_name('CreateTicketInQueue'); - $self->select( 'Queue', $id ); - $self->submit; + $self->get($self->rt_base_url . 'Ticket/Create.html?Queue='.$id); return 1; } sub get_warnings { my $self = shift; - my $server_class = 'RT::Interface::Web::Standalone'; + local $Test::Builder::Level = $Test::Builder::Level + 1; - my $url = $server_class->test_warning_path; + # We clone here so that when we fetch warnings, we don't disrupt the state + # of the test's mech. If we reuse the original mech then you can't + # test warnings immediately after fetching page XYZ, then fill out + # forms on XYZ. This is because the most recently fetched page has changed + # from XYZ to /__test_warnings, which has no form. + my $clone = $self->clone; + return unless $clone->get_ok('/__test_warnings'); - local $Test::Builder::Level = $Test::Builder::Level + 1; - return unless $self->get_ok($url); + use Storable 'thaw'; - my @warnings = $server_class->decode_warnings($self->content); + my @warnings = @{ thaw $clone->content }; return @warnings; } @@ -196,6 +214,26 @@ sub warning_like { return Test::More::like($warnings[0], $re, $name); } +sub next_warning_like { + my $self = shift; + my $re = shift; + my $name = shift; + + local $Test::Builder::Level = $Test::Builder::Level + 1; + + if (@{ $self->{stashed_server_warnings} || [] } == 0) { + my @warnings = $self->get_warnings; + if (@warnings == 0) { + Test::More::fail("no warnings emitted; expected 1"); + return 0; + } + $self->{stashed_server_warnings} = \@warnings; + } + + my $warning = shift @{ $self->{stashed_server_warnings} }; + return Test::More::like($warning, $re, $name); +} + sub no_warnings_ok { my $self = shift; my $name = shift || "no warnings emitted"; @@ -212,4 +250,135 @@ sub no_warnings_ok { return @warnings == 0 ? 1 : 0; } +sub no_leftover_warnings_ok { + my $self = shift; + + my $name = shift || "no leftover warnings"; + + local $Test::Builder::Level = $Test::Builder::Level + 1; + + # we clear the warnings because we don't want to break later tests + # in case there *are* leftover warnings + my @warnings = splice @{ $self->{stashed_server_warnings} || [] }; + + Test::More::is(@warnings, 0, $name); + for (@warnings) { + Test::More::diag("leftover warning: $_"); + } + + return @warnings == 0 ? 1 : 0; +} + +sub ticket_status { + my $self = shift; + my $id = shift; + + $self->display_ticket( $id); + my ($got) = ($self->content =~ m{Status:\s*</td>\s*<td[^>]*?class="value"[^>]*?>\s*([\w ]+?)\s*</td>}ism); + unless ( $got ) { + Test::More::diag("Error: couldn't find status value on the page, may be regexp problem"); + } + return $got; +} + +sub ticket_status_is { + my $self = shift; + my $id = shift; + my $status = shift; + my $desc = shift || "Status of the ticket #$id is '$status'"; + local $Test::Builder::Level = $Test::Builder::Level + 1; + return Test::More::is($self->ticket_status( $id), $status, $desc); +} + +sub get_ticket_id { + my $self = shift; + my $content = $self->content; + my $id = 0; + if ($content =~ /.*Ticket (\d+) created.*/g) { + $id = $1; + } + elsif ($content =~ /.*No permission to view newly created ticket #(\d+).*/g) { + Test::More::diag("\nNo permissions to view the ticket.\n") if($ENV{'TEST_VERBOSE'}); + $id = $1; + } + return $id; +} + +sub set_custom_field { + my $self = shift; + my $queue = shift; + my $cf_name = shift; + my $val = shift; + + my $field_name = $self->custom_field_input( $queue, $cf_name ) + or return 0; + + $self->field($field_name, $val); + return 1; +} + +sub custom_field_input { + my $self = shift; + my $queue = shift; + my $cf_name = shift; + + my $cf_obj = RT::CustomField->new( $RT::SystemUser ); + $cf_obj->LoadByName( Queue => $queue, Name => $cf_name ); + unless ( $cf_obj->id ) { + Test::More::diag("Can not load custom field '$cf_name' in queue '$queue'"); + return undef; + } + my $cf_id = $cf_obj->id; + + my ($res) = + grep /^Object-RT::Ticket-\d*-CustomField-$cf_id-Values?$/, + map $_->name, + $self->current_form->inputs; + unless ( $res ) { + Test::More::diag("Can not find input for custom field '$cf_name' #$cf_id"); + return undef; + } + return $res; +} + +sub check_links { + my $self = shift; + my %args = @_; + + my %has = map {$_ => 1} @{ $args{'has'} }; + my %has_no = map {$_ => 1} @{ $args{'has_no'} }; + + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my @found; + + my @links = $self->followable_links; + foreach my $text ( grep defined && length, map $_->text, @links ) { + push @found, $text if $has_no{ $text }; + delete $has{ $text }; + } + if ( @found || keys %has ) { + Test::More::ok( 0, "expected links" ); + Test::More::diag( "didn't expect, but found: ". join ', ', map "'$_'", @found ) + if @found; + Test::More::diag( "didn't find, but expected: ". join ', ', map "'$_'", keys %has ) + if keys %has; + return 0; + } + return Test::More::ok( 1, "expected links" ); +} + +sub DESTROY { + my $self = shift; + if ( !$RT::Test::Web::DESTROY++ ) { + $self->no_warnings_ok; + } +} + +END { + return unless $instance; + return if RT::Test->builder->{Original_Pid} != $$; + $instance->no_warnings_ok if !$RT::Test::Web::DESTROY++; +} + 1; |
