summaryrefslogtreecommitdiff
path: root/rt/t/web
diff options
context:
space:
mode:
Diffstat (limited to 'rt/t/web')
-rw-r--r--rt/t/web/attachments.t47
-rw-r--r--rt/t/web/basic.t146
-rw-r--r--rt/t/web/cf_access.t191
-rw-r--r--rt/t/web/cf_onqueue.t66
-rw-r--r--rt/t/web/cf_select_one.t159
-rw-r--r--rt/t/web/command_line.t544
-rw-r--r--rt/t/web/command_line_with_unknown_field.t34
-rw-r--r--rt/t/web/compilation_errors.t68
-rw-r--r--rt/t/web/config_tab_right.t41
-rw-r--r--rt/t/web/crypt-gnupg.t446
-rw-r--r--rt/t/web/custom_frontpage.t61
-rw-r--r--rt/t/web/custom_search.t84
-rw-r--r--rt/t/web/dashboard_with_deleted_saved_search.t89
-rw-r--r--rt/t/web/dashboards-groups.t102
-rw-r--r--rt/t/web/dashboards-permissions.t38
-rw-r--r--rt/t/web/dashboards.t250
-rw-r--r--rt/t/web/gnupg-outgoing.t363
-rw-r--r--rt/t/web/gnupg-select-keys-on-create.t325
-rw-r--r--rt/t/web/gnupg-select-keys-on-update.t344
-rw-r--r--rt/t/web/offline_messages_utf8.t67
-rw-r--r--rt/t/web/offline_utf8.t54
-rw-r--r--rt/t/web/query_builder.t249
-rw-r--r--rt/t/web/quicksearch.t51
-rw-r--r--rt/t/web/rest-non-ascii-subject.t55
-rw-r--r--rt/t/web/rest.t71
-rw-r--r--rt/t/web/rights.t85
-rw-r--r--rt/t/web/rights1.t134
-rw-r--r--rt/t/web/saved_search_chart.t86
-rw-r--r--rt/t/web/saved_search_permissions.t34
-rw-r--r--rt/t/web/search_bulk_update_links.t147
-rw-r--r--rt/t/web/ticket-create-utf8.t83
-rw-r--r--rt/t/web/ticket_owner.t356
-rw-r--r--rt/t/web/ticket_seen.t80
-rw-r--r--rt/t/web/ticket_update_without_content.t52
-rw-r--r--rt/t/web/unlimited_search.t41
35 files changed, 5043 insertions, 0 deletions
diff --git a/rt/t/web/attachments.t b/rt/t/web/attachments.t
new file mode 100644
index 000000000..e827b2f02
--- /dev/null
+++ b/rt/t/web/attachments.t
@@ -0,0 +1,47 @@
+#!/usr/bin/perl -w
+use strict;
+
+use RT::Test tests => 14;
+
+use constant LogoFile => $RT::MasonComponentRoot .'/NoAuth/images/bplogo.gif';
+use constant FaviconFile => $RT::MasonComponentRoot .'/NoAuth/images/favicon.png';
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'logged in';
+
+my $queue = RT::Queue->new($RT::Nobody);
+my $qid = $queue->Load('General');
+ok( $qid, "Loaded General queue" );
+
+$m->form_name('CreateTicketInQueue');
+$m->field('Queue', $qid);
+$m->submit;
+is($m->status, 200, "request successful");
+$m->content_like(qr/Create a new ticket/, 'ticket create page');
+
+$m->form_name('TicketCreate');
+$m->field('Subject', 'Attachments test');
+$m->field('Attach', LogoFile);
+$m->field('Content', 'Some content');
+$m->submit;
+is($m->status, 200, "request successful");
+
+$m->content_like(qr/Attachments test/, 'we have subject on the page');
+$m->content_like(qr/Some content/, 'and content');
+$m->content_like(qr/Download bplogo\.gif/, 'page has file name');
+
+$m->follow_link_ok({text => 'Reply'}, "reply to the ticket");
+$m->form_name('TicketUpdate');
+$m->field('Attach', LogoFile);
+$m->click('AddMoreAttach');
+is($m->status, 200, "request successful");
+
+$m->form_name('TicketUpdate');
+$m->field('Attach', FaviconFile);
+$m->field('UpdateContent', 'Message');
+$m->click('SubmitTicket');
+is($m->status, 200, "request successful");
+
+$m->content_like(qr/Download bplogo\.gif/, 'page has file name');
+$m->content_like(qr/Download favicon\.png/, 'page has file name');
+
diff --git a/rt/t/web/basic.t b/rt/t/web/basic.t
new file mode 100644
index 000000000..bc4d65587
--- /dev/null
+++ b/rt/t/web/basic.t
@@ -0,0 +1,146 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Encode;
+
+use RT::Test tests => 24;
+$RT::Test::SKIP_REQUEST_WORK_AROUND = 1;
+
+my ($baseurl, $agent) = RT::Test->started_ok;
+
+my $url = $agent->rt_base_url;
+diag $url if $ENV{TEST_VERBOSE};
+
+# get the top page
+{
+ $agent->get($url);
+ is ($agent->{'status'}, 200, "Loaded a page");
+}
+
+# test a login
+{
+ ok($agent->{form}->find_input('user'));
+ ok($agent->{form}->find_input('pass'));
+
+ ok($agent->{'content'} =~ /username:/i);
+ $agent->field( 'user' => 'root' );
+ $agent->field( 'pass' => 'password' );
+
+ # the field isn't named, so we have to click link 0
+ $agent->click(0);
+ is( $agent->{'status'}, 200, "Fetched the page ok");
+ ok( $agent->{'content'} =~ /Logout/i, "Found a logout link");
+}
+
+{
+ $agent->get($url."Ticket/Create.html?Queue=1");
+ is ($agent->{'status'}, 200, "Loaded Create.html");
+ $agent->form_number(3);
+ my $string = Encode::decode_utf8("I18N Web Testing æøå");
+ $agent->field('Subject' => "Ticket with utf8 body");
+ $agent->field('Content' => $string);
+ ok($agent->submit, "Created new ticket with $string as Content");
+ $agent->content_like( qr{$string} , "Found the content");
+ ok($agent->{redirected_uri}, "Did redirection");
+
+ {
+ my $ticket = RT::Test->last_ticket;
+ my $content = $ticket->Transactions->First->Content;
+ like(
+ $content, qr{$string},
+ 'content is there, API check'
+ );
+ }
+}
+
+{
+ $agent->get($url."Ticket/Create.html?Queue=1");
+ is ($agent->{'status'}, 200, "Loaded Create.html");
+ $agent->form_number(3);
+
+ my $string = Encode::decode_utf8("I18N Web Testing æøå");
+ $agent->field('Subject' => $string);
+ $agent->field('Content' => "Ticket with utf8 subject");
+ ok($agent->submit, "Created new ticket with $string as Content");
+ $agent->content_like( qr{$string} , "Found the content");
+ ok($agent->{redirected_uri}, "Did redirection");
+
+ {
+ my $ticket = RT::Test->last_ticket;
+ is(
+ $ticket->Subject, $string,
+ 'subject is correct, API check'
+ );
+ }
+}
+
+# Update time worked in hours
+{
+ $agent->follow_link( text_regex => qr/Basics/ );
+ $agent->submit_form( form_number => 3,
+ fields => { TimeWorked => 5, 'TimeWorked-TimeUnits' => "hours" }
+ );
+
+ like ($agent->{'content'}, qr/to '300'/, "5 hours is 300 minutes");
+}
+
+# {{{ test an image
+
+TODO: {
+ todo_skip("Need to handle mason trying to compile images",1);
+$agent->get( $url."NoAuth/images/test.png" );
+my $file = RT::Test::get_relocatable_file(
+ File::Spec->catfile(
+ qw(.. .. share html NoAuth images test.png)
+ )
+);
+is(
+ length($agent->content),
+ -s $file,
+ "got a file of the correct size ($file)",
+);
+}
+# }}}
+
+# {{{ Query Builder tests
+#
+# XXX: hey-ho, we have these tests in t/web/query-builder
+# TODO: move everything about QB there
+
+my $response = $agent->get($url."Search/Build.html");
+ok( $response->is_success, "Fetched " . $url."Search/Build.html" );
+
+# Parsing TicketSQL
+#
+# Adding items
+
+# set the first value
+ok($agent->form_name('BuildQuery'));
+$agent->field("AttachmentField", "Subject");
+$agent->field("AttachmentOp", "LIKE");
+$agent->field("ValueOfAttachment", "aaa");
+$agent->submit("AddClause");
+
+# set the next value
+ok($agent->form_name('BuildQuery'));
+$agent->field("AttachmentField", "Subject");
+$agent->field("AttachmentOp", "LIKE");
+$agent->field("ValueOfAttachment", "bbb");
+$agent->submit("AddClause");
+
+ok($agent->form_name('BuildQuery'));
+
+# get the query
+my $query = $agent->current_form->find_input("Query")->value;
+# strip whitespace from ends
+$query =~ s/^\s*//g;
+$query =~ s/\s*$//g;
+
+# collapse other whitespace
+$query =~ s/\s+/ /g;
+
+is ($query, "Subject LIKE 'aaa' AND Subject LIKE 'bbb'");
+
+
+1;
diff --git a/rt/t/web/cf_access.t b/rt/t/web/cf_access.t
new file mode 100644
index 000000000..1022c6da6
--- /dev/null
+++ b/rt/t/web/cf_access.t
@@ -0,0 +1,191 @@
+#!/usr/bin/perl -w
+use strict;
+
+use RT::Test tests => 26;
+$RT::Test::SKIP_REQUEST_WORK_AROUND = 1;
+
+my ($baseurl, $m) = RT::Test->started_ok;
+
+use constant ImageFile => $RT::MasonComponentRoot .'/NoAuth/images/bplogo.gif';
+use constant ImageFileContent => RT::Test->file_content(ImageFile);
+
+ok $m->login, 'logged in';
+
+diag "Create a CF" if $ENV{'TEST_VERBOSE'};
+{
+ $m->follow_link( text => 'Configuration' );
+ $m->title_is(q/RT Administration/, 'admin screen');
+ $m->follow_link( text => 'Custom Fields' );
+ $m->title_is(q/Select a Custom Field/, 'admin-cf screen');
+ $m->follow_link( text => 'Create' );
+ $m->submit_form(
+ form_name => "ModifyCustomField",
+ fields => {
+ TypeComposite => 'Image-0',
+ LookupType => 'RT::Queue-RT::Ticket',
+ Name => 'img',
+ Description => 'img',
+ },
+ );
+}
+
+diag "apply the CF to General queue" if $ENV{'TEST_VERBOSE'};
+my ( $cf, $cfid, $tid );
+{
+ $m->title_is(q/Created CustomField img/, 'admin-cf created');
+ $m->follow_link( text => 'Queues' );
+ $m->title_is(q/Admin queues/, 'admin-queues screen');
+ $m->follow_link( text => 'General' );
+ $m->title_is(q/Editing Configuration for queue General/, 'admin-queue: general');
+ $m->follow_link( text => 'Ticket Custom Fields' );
+
+ $m->title_is(q/Edit Custom Fields for General/, 'admin-queue: general cfid');
+ $m->form_name('EditCustomFields');
+
+ # Sort by numeric IDs in names
+ my @names = map { $_->[1] }
+ sort { $a->[0] <=> $b->[0] }
+ map { /Object-1-CF-(\d+)/ ? [ $1 => $_ ] : () }
+ grep defined, map $_->name, $m->current_form->inputs;
+ $cf = pop(@names);
+ $cf =~ /(\d+)$/ or die "Hey this is impossible dude";
+ $cfid = $1;
+ $m->field( $cf => 1 ); # Associate the new CF with this queue
+ $m->field( $_ => undef ) for @names; # ...and not any other. ;-)
+ $m->submit;
+
+ $m->content_like( qr/Object created/, 'TCF added to the queue' );
+}
+
+my $tester = RT::Test->load_or_create_user( Name => 'tester', Password => '123456' );
+RT::Test->set_rights(
+ { Principal => $tester->PrincipalObj,
+ Right => [qw(SeeQueue ShowTicket CreateTicket)],
+ },
+);
+ok $m->login( $tester->Name, 123456), 'logged in';
+
+diag "check that we have no the CF on the create"
+ ." ticket page when user has no SeeCustomField right"
+ if $ENV{'TEST_VERBOSE'};
+{
+ $m->submit_form(
+ form_name => "CreateTicketInQueue",
+ fields => { Queue => 'General' },
+ );
+ $m->content_unlike(qr/Upload multiple images/, 'has no upload image field');
+
+ my $form = $m->form_name("TicketCreate");
+ my $upload_field = "Object-RT::Ticket--CustomField-$cfid-Upload";
+ ok !$form->find_input( $upload_field ), 'no form field on the page';
+
+ $m->submit_form(
+ form_name => "TicketCreate",
+ fields => { Subject => 'test' },
+ );
+ $m->content_like(qr/Ticket \d+ created/, "a ticket is created succesfully");
+
+ $m->content_unlike(qr/img:/, 'has no img field on the page');
+ $m->follow_link( text => 'Custom Fields');
+ $m->content_unlike(qr/Upload multiple images/, 'has no upload image field');
+}
+
+RT::Test->set_rights(
+ { Principal => $tester->PrincipalObj,
+ Right => [qw(SeeQueue ShowTicket CreateTicket SeeCustomField)],
+ },
+);
+
+diag "check that we have no the CF on the create"
+ ." ticket page when user has no ModifyCustomField right"
+ if $ENV{'TEST_VERBOSE'};
+{
+ $m->submit_form(
+ form_name => "CreateTicketInQueue",
+ fields => { Queue => 'General' },
+ );
+ $m->content_unlike(qr/Upload multiple images/, 'has no upload image field');
+
+ my $form = $m->form_name("TicketCreate");
+ my $upload_field = "Object-RT::Ticket--CustomField-$cfid-Upload";
+ ok !$form->find_input( $upload_field ), 'no form field on the page';
+
+ $m->submit_form(
+ form_name => "TicketCreate",
+ fields => { Subject => 'test' },
+ );
+ $tid = $1 if $m->content =~ /Ticket (\d+) created/i;
+ ok $tid, "a ticket is created succesfully";
+
+ $m->follow_link( text => 'Custom Fields' );
+ $m->content_unlike(qr/Upload multiple images/, 'has no upload image field');
+ $form = $m->form_number(3);
+ $upload_field = "Object-RT::Ticket-$tid-CustomField-$cfid-Upload";
+ ok !$form->find_input( $upload_field ), 'no form field on the page';
+}
+
+RT::Test->set_rights(
+ { Principal => $tester->PrincipalObj,
+ Right => [qw(SeeQueue ShowTicket CreateTicket SeeCustomField ModifyCustomField)],
+ },
+);
+
+diag "create a ticket with an image" if $ENV{'TEST_VERBOSE'};
+{
+ $m->submit_form(
+ form_name => "CreateTicketInQueue",
+ fields => { Queue => 'General' },
+ );
+ $m->content_like(qr/Upload multiple images/, 'has a upload image field');
+
+ $cf =~ /(\d+)$/ or die "Hey this is impossible dude";
+ my $upload_field = "Object-RT::Ticket--CustomField-$1-Upload";
+
+ $m->submit_form(
+ form_name => "TicketCreate",
+ fields => {
+ $upload_field => ImageFile,
+ Subject => 'testing img cf creation',
+ },
+ );
+
+ $m->content_like(qr/Ticket \d+ created/, "a ticket is created succesfully");
+
+ $tid = $1 if $m->content =~ /Ticket (\d+) created/;
+
+ $m->title_like(qr/testing img cf creation/, "its title is the Subject");
+
+ $m->follow_link( text => 'bplogo.gif' );
+ $m->content_is(ImageFileContent, "it links to the uploaded image");
+}
+
+$m->get( $m->rt_base_url );
+$m->follow_link( text => 'Tickets' );
+$m->follow_link( text => 'New Query' );
+
+$m->title_is(q/Query Builder/, 'Query building');
+$m->submit_form(
+ form_name => "BuildQuery",
+ fields => {
+ idOp => '=',
+ ValueOfid => $tid,
+ ValueOfQueue => 'General',
+ },
+ button => 'AddClause',
+);
+
+$m->form_name('BuildQuery');
+
+my $col = ($m->current_form->find_input('SelectDisplayColumns'))[-1];
+$col->value( ($col->possible_values)[-1] );
+
+$m->click('AddCol');
+
+$m->form_name('BuildQuery');
+$m->click('DoSearch');
+
+$m->follow_link( text_regex => qr/bplogo\.gif/ );
+$m->content_is(ImageFileContent, "it links to the uploaded image");
+
+__END__
+[FC] Bulk Update does not have custom fields.
diff --git a/rt/t/web/cf_onqueue.t b/rt/t/web/cf_onqueue.t
new file mode 100644
index 000000000..dcd585277
--- /dev/null
+++ b/rt/t/web/cf_onqueue.t
@@ -0,0 +1,66 @@
+#!/usr/bin/perl -w
+use strict;
+
+use RT::Test tests => 14;
+my ($baseurl, $m) = RT::Test->started_ok;
+
+ok $m->login, 'logged in';
+
+diag "Create a queue CF" if $ENV{'TEST_VERBOSE'};
+{
+ $m->follow_link( text => 'Configuration' );
+ $m->title_is(q/RT Administration/, 'admin screen');
+ $m->follow_link( text => 'Custom Fields' );
+ $m->title_is(q/Select a Custom Field/, 'admin-cf screen');
+ $m->follow_link( text => 'Create' );
+ $m->submit_form(
+ form_name => "ModifyCustomField",
+ fields => {
+ TypeComposite => 'Freeform-1',
+ LookupType => 'RT::Queue',
+ Name => 'QueueCFTest',
+ Description => 'QueueCFTest',
+ },
+ );
+ $m->content_like( qr/Object created/, 'CF QueueCFTest created' );
+}
+
+diag "Apply the new CF globally" if $ENV{'TEST_VERBOSE'};
+{
+ $m->follow_link( text => 'Global' );
+ $m->title_is(q!Admin/Global configuration!, 'global configuration screen');
+ $m->follow_link( url_regex => qr!Admin/Global/CustomFields/index! );
+ $m->title_is(q/Global custom field configuration/, 'global custom field configuration screen');
+ $m->follow_link( url => 'Queues.html' );
+ $m->title_is(q/Edit Custom Fields for all queues/, 'global custom field for all queues configuration screen');
+ $m->content_like( qr/QueueCFTest/, 'CF QueueCFTest displayed on page' );
+ $m->submit_form(
+ form_name => "EditCustomFields",
+ fields => {
+ 'Object--CF-1' => '1',
+ },
+ );
+ $m->content_like( qr/Object created/, 'CF QueueCFTest enabled globally' );
+}
+
+diag "Edit the CF value for default queue" if $ENV{'TEST_VERBOSE'};
+{
+ $m->follow_link( url => '/Admin/Queues/' );
+ $m->title_is(q/Admin queues/, 'queues configuration screen');
+ $m->follow_link( text => "1" );
+ $m->title_is(q/Editing Configuration for queue General/, 'default queue configuration screen');
+ $m->content_like( qr/QueueCFTest/, 'CF QueueCFTest displayed on default queue' );
+ $m->submit_form(
+ form_number => 3,
+ # The following doesn't want to works :(
+ #with_fields => { 'Object-RT::Queue-1-CustomField-1-Value' },
+ fields => {
+ 'Object-RT::Queue-1-CustomField-1-Value' => 'QueueCFTest content',
+ },
+ );
+ $m->content_like( qr/QueueCFTest QueueCFTest content added/, 'Content filed in CF QueueCFTest for default queue' );
+
+}
+
+
+__END__
diff --git a/rt/t/web/cf_select_one.t b/rt/t/web/cf_select_one.t
new file mode 100644
index 000000000..e009af7cf
--- /dev/null
+++ b/rt/t/web/cf_select_one.t
@@ -0,0 +1,159 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use RT::Test tests => 41;
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'logged in as root';
+
+my $cf_name = 'test select one value';
+
+my $cfid;
+diag "Create a CF" if $ENV{'TEST_VERBOSE'};
+{
+ $m->follow_link( text => 'Configuration' );
+ $m->title_is(q/RT Administration/, 'admin screen');
+ $m->follow_link( text => 'Custom Fields' );
+ $m->title_is(q/Select a Custom Field/, 'admin-cf screen');
+ $m->follow_link( text => 'Create' );
+ $m->submit_form(
+ form_name => "ModifyCustomField",
+ fields => {
+ Name => $cf_name,
+ TypeComposite => 'Select-1',
+ LookupType => 'RT::Queue-RT::Ticket',
+ },
+ );
+ $m->content_like( qr/Object created/, 'created CF sucessfully' );
+ $cfid = $m->form_name('ModifyCustomField')->value('id');
+ ok $cfid, "found id of the CF in the form, it's #$cfid";
+}
+
+diag "add 'qwe', 'ASD' and '0' as values to the CF" if $ENV{'TEST_VERBOSE'};
+{
+ foreach my $value(qw(qwe ASD 0)) {
+ $m->submit_form(
+ form_name => "ModifyCustomField",
+ fields => {
+ "CustomField-". $cfid ."-Value-new-Name" => $value,
+ },
+ button => 'Update',
+ );
+ $m->content_like( qr/Object created/, 'added a value to the CF' ); # or diag $m->content;
+ }
+}
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+ok $queue && $queue->id, 'loaded or created queue';
+
+diag "apply the CF to General queue" if $ENV{'TEST_VERBOSE'};
+{
+ $m->follow_link( text => 'Queues' );
+ $m->title_is(q/Admin queues/, 'admin-queues screen');
+ $m->follow_link( text => 'General' );
+ $m->title_is(q/Editing Configuration for queue General/, 'admin-queue: general');
+ $m->follow_link( text => 'Ticket Custom Fields' );
+ $m->title_is(q/Edit Custom Fields for General/, 'admin-queue: general cfid');
+
+ $m->form_name('EditCustomFields');
+ $m->field( "Object-". $queue->id ."-CF-$cfid" => 1 );
+ $m->submit;
+
+ $m->content_like( qr/Object created/, 'TCF added to the queue' );
+}
+
+my $tid;
+diag "create a ticket using API with 'asd'(not 'ASD') as value of the CF"
+ if $ENV{'TEST_VERBOSE'};
+{
+ my $ticket = RT::Ticket->new( $RT::SystemUser );
+ my ($txnid, $msg);
+ ($tid, $txnid, $msg) = $ticket->Create(
+ Subject => 'test',
+ Queue => $queue->id,
+ "CustomField-$cfid" => 'asd',
+ );
+ ok $tid, "created ticket";
+ diag $msg if $msg && $ENV{'TEST_VERBOSE'};
+
+ # we use lc as we really don't care about case
+ # so if later we'll add canonicalization of value
+ # test should work
+ is lc $ticket->FirstCustomFieldValue( $cf_name ),
+ 'asd', 'assigned value of the CF';
+}
+
+diag "check that values of the CF are case insensetive(asd vs. ASD)"
+ if $ENV{'TEST_VERBOSE'};
+{
+ ok $m->goto_ticket( $tid ), "opened ticket's page";
+ $m->follow_link( text => 'Custom Fields' );
+ $m->title_like(qr/Modify ticket/i, 'modify ticket');
+ $m->content_like(qr/\Q$cf_name/, 'CF on the page');
+
+ my $value = $m->form_number(3)->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values");
+ is lc $value, 'asd', 'correct value is selected';
+ $m->submit;
+ $m->content_unlike(qr/\Q$cf_name\E.*?changed/mi, 'field is not changed');
+
+ $value = $m->form_number(3)->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values");
+ is lc $value, 'asd', 'the same value is still selected';
+
+ my $ticket = RT::Ticket->new( $RT::SystemUser );
+ $ticket->Load( $tid );
+ ok $ticket->id, 'loaded the ticket';
+ is lc $ticket->FirstCustomFieldValue( $cf_name ),
+ 'asd', 'value is still the same';
+}
+
+diag "check that 0 is ok value of the CF"
+ if $ENV{'TEST_VERBOSE'};
+{
+ ok $m->goto_ticket( $tid ), "opened ticket's page";
+ $m->follow_link( text => 'Custom Fields' );
+ $m->title_like(qr/Modify ticket/i, 'modify ticket');
+ $m->content_like(qr/\Q$cf_name/, 'CF on the page');
+
+ my $value = $m->form_number(3)->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values");
+ is lc $value, 'asd', 'correct value is selected';
+ $m->select("Object-RT::Ticket-$tid-CustomField-$cfid-Values" => 0 );
+ $m->submit;
+ $m->content_like(qr/\Q$cf_name\E.*?changed/mi, 'field is changed');
+ $m->content_unlike(qr/0 is no longer a value for custom field/mi, 'no bad message in results');
+
+ $value = $m->form_number(3)->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values");
+ is lc $value, '0', 'new value is selected';
+
+ my $ticket = RT::Ticket->new( $RT::SystemUser );
+ $ticket->Load( $tid );
+ ok $ticket->id, 'loaded the ticket';
+ is lc $ticket->FirstCustomFieldValue( $cf_name ),
+ '0', 'API returns correct value';
+}
+
+diag "check that we can set empty value when the current is 0"
+ if $ENV{'TEST_VERBOSE'};
+{
+ ok $m->goto_ticket( $tid ), "opened ticket's page";
+ $m->follow_link( text => 'Custom Fields' );
+ $m->title_like(qr/Modify ticket/i, 'modify ticket');
+ $m->content_like(qr/\Q$cf_name/, 'CF on the page');
+
+ my $value = $m->form_number(3)->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values");
+ is lc $value, '0', 'correct value is selected';
+ $m->select("Object-RT::Ticket-$tid-CustomField-$cfid-Values" => '' );
+ $m->submit;
+ $m->content_like(qr/0 is no longer a value for custom field/mi, '0 is no longer a value');
+
+ $value = $m->form_number(3)->value("Object-RT::Ticket-$tid-CustomField-$cfid-Values");
+ is $value, '', '(no value) is selected';
+
+ my $ticket = RT::Ticket->new( $RT::SystemUser );
+ $ticket->Load( $tid );
+ ok $ticket->id, 'loaded the ticket';
+ is $ticket->FirstCustomFieldValue( $cf_name ),
+ undef, 'API returns correct value';
+}
+
diff --git a/rt/t/web/command_line.t b/rt/t/web/command_line.t
new file mode 100644
index 000000000..3fc279bf3
--- /dev/null
+++ b/rt/t/web/command_line.t
@@ -0,0 +1,544 @@
+#!/usr/bin/perl -w
+
+use strict;
+use File::Spec ();
+use Test::Expect;
+use RT::Test tests => 295;
+my ($baseurl, $m) = RT::Test->started_ok;
+
+use RT::User;
+use RT::Queue;
+
+my $rt_tool_path = "$RT::BinPath/rt";
+
+# {{{ test configuration options
+
+# config directives:
+# (in $CWD/.rtrc)
+# - server <URL> URL to RT server.
+# - user <username> RT username.
+# - passwd <passwd> RT user's password.
+# - query <RT Query> Default RT Query for list action
+# - orderby <order> Default RT order for list action
+#
+# Blank and #-commented lines are ignored.
+
+# environment variables
+# The following environment variables override any corresponding
+# values defined in configuration files:
+#
+# - RTUSER
+$ENV{'RTUSER'} = 'root';
+# - RTPASSWD
+$ENV{'RTPASSWD'} = 'password';
+# - RTSERVER
+$RT::Logger->debug("Connecting to server at ".RT->Config->Get('WebBaseURL'));
+$ENV{'RTSERVER'} =RT->Config->Get('WebBaseURL') ;
+# - RTDEBUG Numeric debug level. (Set to 3 for full logs.)
+$ENV{'RTDEBUG'} = '1';
+# - RTCONFIG Specifies a name other than ".rtrc" for the
+# configuration file.
+#
+# - RTQUERY Default RT Query for rt list
+# - RTORDERBY Default order for rt list
+
+
+# }}}
+
+# {{{ test ticket manipulation
+
+# create a ticket
+expect_run(
+ command => "$rt_tool_path shell",
+ prompt => 'rt> ',
+ quit => 'quit',
+);
+expect_send(q{create -t ticket set subject='new ticket' add cc=foo@example.com}, "Creating a ticket...");
+expect_like(qr/Ticket \d+ created/, "Created the ticket");
+expect_handle->before() =~ /Ticket (\d+) created/;
+my $ticket_id = $1;
+ok($ticket_id, "Got ticket id=$ticket_id");
+expect_send(q{create -t ticket set subject='new ticket'}, "Creating a ticket as just a subject...");
+expect_like(qr/Ticket \d+ created/, "Created the ticket");
+
+# make sure we can request things as 'rt foo'
+expect_send(q{rt create -t ticket set subject='rt ticket'}, "Creating a ticket with 'rt create'...");
+expect_like(qr/Ticket \d+ created/, "Created the ticket");
+
+# {{{ test queue manipulation
+
+# creating queues
+expect_send("create -t queue set Name='NewQueue$$'", 'Creating a queue...');
+expect_like(qr/Queue \d+ created/, 'Created the queue');
+expect_handle->before() =~ /Queue (\d+) created/;
+my $queue_id = $1;
+ok($queue_id, "Got queue id=$queue_id");
+# updating users
+expect_send("edit queue/$queue_id set Name='EditedQueue$$'", 'Editing the queue');
+expect_like(qr/Queue $queue_id updated/, 'Edited the queue');
+expect_send("show queue/$queue_id", 'Showing the queue...');
+expect_like(qr/id: queue\/$queue_id/, 'Saw the queue');
+expect_like(qr/Name: EditedQueue$$/, 'Saw the modification');
+TODO: {
+ todo_skip "Listing non-ticket items doesn't work", 2;
+ expect_send("list -t queue 'id > 0'", 'Listing the queues...');
+ expect_like(qr/$queue_id: EditedQueue$$/, 'Found the queue');
+}
+
+# }}}
+
+
+# Set up a custom field for editing tests
+my $cf = RT::CustomField->new($RT::SystemUser);
+my ($val,$msg) = $cf->Create(Name => 'MyCF'.$$, Type => 'FreeformSingle', Queue => $queue_id);
+ok($val,$msg);
+
+my $othercf = RT::CustomField->new($RT::SystemUser);
+($val,$msg) = $othercf->Create(Name => 'My CF'.$$, Type => 'FreeformSingle', Queue => $queue_id);
+ok($val,$msg);
+
+my $multiple_cf = RT::CustomField->new($RT::SystemUser);
+($val,$msg) = $multiple_cf->Create(Name => 'MultipleCF'.$$, Type =>
+ 'FreeformMultiple', Queue => $queue_id);
+ok($val,$msg);
+
+
+# add a comment to ticket
+ expect_send("comment -m 'comment-$$' $ticket_id", "Adding a comment...");
+ expect_like(qr/Message recorded/, "Added the comment");
+ ### should test to make sure it actually got added
+ # add correspondance to ticket (?)
+ expect_send("correspond -m 'correspond-$$' $ticket_id", "Adding correspondence...");
+ expect_like(qr/Message recorded/, "Added the correspondence");
+ ### should test to make sure it actually got added
+
+ my $test_email = RT::Test::get_relocatable_file('lorem-ipsum',
+ (File::Spec->updir(), 'data', 'emails'));
+ # add attachments to a ticket
+ # text attachment
+ check_attachment($test_email);
+ # binary attachment
+ check_attachment($RT::MasonComponentRoot.'/NoAuth/images/bplogo.gif');
+
+# change a ticket's Owner
+expect_send("edit ticket/$ticket_id set owner=root", 'Changing owner...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed owner');
+expect_send("show ticket/$ticket_id -f owner", 'Verifying change...');
+expect_like(qr/Owner: root/, 'Verified change');
+# change a ticket's Requestor
+expect_send("edit ticket/$ticket_id set requestors=foo\@example.com", 'Changing Requestor...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed Requestor');
+expect_send("show ticket/$ticket_id -f requestors", 'Verifying change...');
+expect_like(qr/Requestors: foo\@example.com/, 'Verified change');
+# change a ticket's Cc
+expect_send("edit ticket/$ticket_id set cc=bar\@example.com", 'Changing Cc...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed Cc');
+expect_send("show ticket/$ticket_id -f cc", 'Verifying change...');
+expect_like(qr/Cc: bar\@example.com/, 'Verified change');
+# change a ticket's priority
+expect_send("edit ticket/$ticket_id set priority=10", 'Changing priority...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed priority');
+expect_send("show ticket/$ticket_id -f priority", 'Verifying change...');
+expect_like(qr/Priority: 10/, 'Verified change');
+# move a ticket to a different queue
+expect_send("edit ticket/$ticket_id set queue=EditedQueue$$", 'Changing queue...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed queue');
+expect_send("show ticket/$ticket_id -f queue", 'Verifying change...');
+expect_like(qr/Queue: EditedQueue$$/, 'Verified change');
+# cannot move ticket to a nonexistent queue
+expect_send("edit ticket/$ticket_id set queue=nonexistent-$$", 'Changing to nonexistent queue...');
+expect_like(qr/queue does not exist/i, 'Errored out');
+expect_send("show ticket/$ticket_id -f queue", 'Verifying lack of change...');
+expect_like(qr/Queue: EditedQueue$$/, 'Verified lack of change');
+
+# Test reading and setting custom fields without spaces
+expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking initial value');
+expect_like(qr/CF\.{myCF$$}:/i, 'Verified initial empty value (CF-x syntax)');
+expect_send("show ticket/$ticket_id -f CF.{myCF$$}", 'Checking initial value');
+expect_like(qr/CF\.{myCF$$}:/i, 'Verified initial empty value (CF.{x} syntax)');
+
+expect_send("edit ticket/$ticket_id set 'CF-myCF$$=VALUE' ", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed cf');
+expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking new value');
+expect_like(qr/CF\.{myCF$$}: VALUE/i, 'Verified change');
+# Test setting 0 as value of the custom field
+expect_send("edit ticket/$ticket_id set 'CF-myCF$$=0' ", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed cf');
+expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking new value');
+expect_like(qr/CF\.{myCF$$}: 0/i, 'Verified change');
+
+expect_send("edit ticket/$ticket_id set 'CF.{myCF$$}=VALUE' ",'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed cf');
+expect_send("show ticket/$ticket_id -f CF.{myCF$$}", 'Checking new value');
+expect_like(qr/CF\.{myCF$$}: VALUE/i, 'Verified change');
+# Test setting 0 as value of the custom field
+expect_send("edit ticket/$ticket_id set 'CF.{myCF$$}=0' ", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed cf');
+expect_send("show ticket/$ticket_id -f CF.{myCF$$}", 'Checking new value');
+expect_like(qr/CF\.{myCF$$}: 0/i, 'Verified change');
+
+# Test reading and setting custom fields with spaces
+expect_send("show ticket/$ticket_id -f 'CF-my CF$$'", 'Checking initial value');
+expect_like(qr/CF\.{my CF$$}:/i, 'Verified change');
+expect_send("edit ticket/$ticket_id set 'CF-my CF$$=VALUE' ", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed cf');
+expect_send("show ticket/$ticket_id -f 'CF-my CF$$'", 'Checking new value');
+expect_like(qr/CF\.{my CF$$}: VALUE/i, 'Verified change');
+expect_send("ls -l 'id = $ticket_id' -f 'CF-my CF$$'", 'Checking new value');
+expect_like(qr/CF\.{my CF$$}: VALUE/i, 'Verified change');
+
+expect_send("show ticket/$ticket_id -f 'CF.{my CF$$}'", 'Checking initial value');
+expect_like(qr/CF\.{my CF$$}: VALUE/i, 'Verified change');
+expect_send("edit ticket/$ticket_id set 'CF.{my CF$$}=NEW' ", 'Changing CF...');
+expect_send("show ticket/$ticket_id -f 'CF.{my CF$$}'", 'Checking new value');
+expect_like(qr/CF\.{my CF$$}: NEW/i, 'Verified change');
+expect_send("ls -l 'id = $ticket_id' -f 'CF.{my CF$$}'", 'Checking new value');
+expect_like(qr/CF\.{my CF$$}: NEW/i, 'Verified change');
+
+# Test reading and setting single value custom field with commas or quotes
+expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking initial value');
+expect_like(qr/CF\.{myCF$$}:/i, 'Verified change');
+expect_send("edit ticket/$ticket_id set CF-myCF$$=1,2,3", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed cf');
+expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking new value');
+expect_like(qr/CF\.{myCF$$}: 1,2,3/i, 'Verified change');
+expect_send("edit ticket/$ticket_id set CF-myCF$$=\"1's,2,3\"", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed cf');
+expect_send("show ticket/$ticket_id -f CF-myCF$$", 'Checking new value');
+expect_like(qr/CF\.{myCF$$}: 1's,2,3/i, 'Verified change');
+
+# Test reading and setting custom fields with multiple values
+expect_send("show ticket/$ticket_id -f CF-MultipleCF$$", 'Checking initial value');
+expect_like(qr/CF\.{MultipleCF$$}:/i, 'Verified multiple cf change');
+expect_send("edit ticket/$ticket_id set CF.{MultipleCF$$}=1,2,3 ", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf');
+expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value');
+expect_like(qr/CF\.{MultipleCF$$}: 1,\s*2,\s*3/i, 'Verified multiple cf change');
+expect_send("edit ticket/$ticket_id set CF.{MultipleCF$$}=a,b,c ", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf');
+expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value');
+expect_like(qr/CF\.{MultipleCF$$}: a,\s*b,\s*c/i, 'Verified change');
+expect_send("edit ticket/$ticket_id del CF.{MultipleCF$$}=a", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'del multiple cf');
+expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value');
+expect_like(qr/CF\.{MultipleCF$$}: b,\s*c/i, 'Verified multiple cf change');
+expect_send("edit ticket/$ticket_id add CF.{MultipleCF$$}=o", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf');
+expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value');
+expect_like(qr/CF\.{MultipleCF$$}: b,\s*c,\s*o/i, 'Verified multiple cf change');
+
+expect_send("edit ticket/$ticket_id set CF.{MultipleCF$$}=\"'a,b,c'\" ", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf');
+expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value');
+expect_like(qr/CF\.{MultipleCF$$}: 'a,b,c'/i, 'Verified change');
+expect_send("edit ticket/$ticket_id del CF.{MultipleCF$$}=a", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf');
+expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value');
+expect_like(qr/CF\.{MultipleCF$$}: 'a,b,c'/i, 'Verified change');
+
+expect_send("edit ticket/$ticket_id set CF.{MultipleCF$$}=q{a,b,c}", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf');
+expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value');
+expect_like(qr/CF\.{MultipleCF$$}: 'a,b,c'/i, 'Verified change');
+expect_send("edit ticket/$ticket_id del CF.{MultipleCF$$}=a", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf');
+expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value');
+expect_like(qr/CF\.{MultipleCF$$}: 'a,b,c'/i, 'Verified change');
+expect_send("edit ticket/$ticket_id del CF.{MultipleCF$$}=\"'a,b,c'\"", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf');
+expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value');
+expect_like(qr/CF\.{MultipleCF$$}: \s*$/i, 'Verified change');
+
+expect_send("edit ticket/$ticket_id set CF.{MultipleCF$$}=\"q{1,2's,3}\"", 'Changing CF...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed multiple cf');
+expect_send("show ticket/$ticket_id -f CF.{MultipleCF$$}", 'Checking new value');
+expect_like(qr/CF\.{MultipleCF$$}: '1,2\\'s,3'/i, 'Verified change');
+
+# ...
+# change a ticket's ...[other properties]...
+# ...
+# stall a ticket
+expect_send("edit ticket/$ticket_id set status=stalled", 'Changing status to "stalled"...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed status');
+expect_send("show ticket/$ticket_id -f status", 'Verifying change...');
+expect_like(qr/Status: stalled/, 'Verified change');
+# resolve a ticket
+expect_send("edit ticket/$ticket_id set status=resolved", 'Changing status to "resolved"...');
+expect_like(qr/Ticket $ticket_id updated/, 'Changed status');
+expect_send("show ticket/$ticket_id -f status", 'Verifying change...');
+expect_like(qr/Status: resolved/, 'Verified change');
+# try to set status to an illegal value
+expect_send("edit ticket/$ticket_id set status=quux", 'Changing status to an illegal value...');
+expect_like(qr/illegal value/i, 'Errored out');
+expect_send("show ticket/$ticket_id -f status", 'Verifying lack of change...');
+expect_like(qr/Status: resolved/, 'Verified change');
+
+# }}}
+
+# {{{ display
+
+# show ticket list
+expect_send("ls -s -t ticket -o +id \"Status='resolved'\"", 'Listing resolved tickets...');
+expect_like(qr/$ticket_id: new ticket/, 'Found our ticket');
+# show ticket list verbosely
+expect_send("ls -l -t ticket -o +id \"Status='resolved'\"", 'Listing resolved tickets verbosely...');
+expect_like(qr/id: ticket\/$ticket_id/, 'Found our ticket');
+# show ticket
+expect_send("show -s -t ticket $ticket_id", 'Showing our ticket...');
+expect_like(qr/id: ticket\/$ticket_id/, 'Got our ticket');
+# show ticket history
+expect_send("show ticket/$ticket_id/history", 'Showing our ticket\'s history...');
+expect_like(qr/Ticket created by root/, 'Got our history');
+
+expect_send("show -v ticket/$ticket_id/history", 'Showing our ticket\'s history verbosely...');
+TODO: {
+ local $TODO = "Cannot show verbose ticket history right now";
+ # show ticket history verbosely
+ expect_like(qr/Ticket created by root/, 'Got our history');
+}
+# get attachments from a ticket
+expect_send("show -s ticket/$ticket_id/attachments", 'Showing ticket attachments...');
+expect_like(qr/id: ticket\/$ticket_id\/attachments/, 'Got our ticket\'s attachments');
+expect_like(qr/Attachments: \d+: \(Unnamed\) \(\S+ \/ \d+\w+\)/, 'Our ticket has an attachment');
+expect_handle->before() =~ /Attachments: (\d+): \(Unnamed\) \((\S+)/;
+my $attachment_id = $1;
+my $attachment_type = $2;
+ok($attachment_id, "Got attachment id=$attachment_id $attachment_type");
+expect_send("show -s ticket/$ticket_id/attachments/$attachment_id", "Showing attachment $attachment_id...");
+expect_like(qr/ContentType: $attachment_type/, 'Got the attachment');
+
+# }}}
+
+# {{{ test user manipulation
+
+# creating users
+expect_send("create -t user set Name='NewUser$$' EmailAddress='fbar$$\@example.com'", 'Creating a user...');
+expect_like(qr/User \d+ created/, 'Created the user');
+expect_handle->before() =~ /User (\d+) created/;
+my $user_id = $1;
+ok($user_id, "Got user id=$user_id");
+# updating users
+expect_send("edit user/$user_id set Name='EditedUser$$'", 'Editing the user');
+expect_like(qr/User $user_id updated/, 'Edited the user');
+expect_send("show user/$user_id", 'Showing the user...');
+expect_like(qr/id: user\/$user_id/, 'Saw the user');
+expect_like(qr/Name: EditedUser$$/, 'Saw the modification');
+TODO: {
+ todo_skip "Listing non-ticket items doesn't work", 2;
+ expect_send("list -t user 'id > 0'", 'Listing the users...');
+ expect_like(qr/$user_id: EditedUser$$/, 'Found the user');
+}
+
+# }}}
+
+# {{{ test group manipulation
+
+TODO: {
+todo_skip "Group manipulation doesn't work right now", 8;
+# creating groups
+expect_send("create -t group set Name='NewGroup$$'", 'Creating a group...');
+expect_like(qr/Group \d+ created/, 'Created the group');
+expect_handle->before() =~ /Group (\d+) created/;
+my $group_id = $1;
+ok($group_id, "Got group id=$group_id");
+# updating groups
+expect_send("edit group/$group_id set Name='EditedGroup$$'", 'Editing the group');
+expect_like(qr/Group $group_id updated/, 'Edited the group');
+expect_send("show group/$group_id", 'Showing the group...');
+expect_like(qr/id: group\/$group_id/, 'Saw the group');
+expect_like(qr/Name: EditedGroup$$/, 'Saw the modification');
+TODO: {
+ local $TODO = "Listing non-ticket items doesn't work";
+ expect_send("list -t group 'id > 0'", 'Listing the groups...');
+ expect_like(qr/$group_id: EditedGroup$$/, 'Found the group');
+}
+}
+
+# }}}
+
+TODO: {
+todo_skip "Custom field manipulation not yet implemented", 8;
+# {{{ test custom field manipulation
+
+# creating custom fields
+expect_send("create -t custom_field set Name='NewCF$$'", 'Creating a custom field...');
+expect_like(qr/Custom Field \d+ created/, 'Created the custom field');
+expect_handle->before() =~ /Custom Field (\d+) created/;
+my $cf_id = $1;
+ok($cf_id, "Got custom field id=$cf_id");
+# updating custom fields
+expect_send("edit cf/$cf_id set Name='EditedCF$$'", 'Editing the custom field');
+expect_like(qr/Custom field $cf_id updated/, 'Edited the custom field');
+expect_send("show cf/$cf_id", 'Showing the queue...');
+expect_like(qr/id: custom_field\/$cf_id/, 'Saw the custom field');
+expect_like(qr/Name: EditedCF$$/, 'Saw the modification');
+TODO: {
+ todo_skip "Listing non-ticket items doesn't work", 2;
+ expect_send("list -t custom_field 'id > 0'", 'Listing the CFs...');
+ expect_like(qr/$cf_id: EditedCF$$/, 'Found the custom field');
+}
+}
+
+# }}}
+
+# {{{ test merging tickets
+expect_send("create -t ticket set subject='CLIMergeTest1-$$'", 'Creating first ticket to merge...');
+expect_like(qr/Ticket \d+ created/, 'Created first ticket');
+expect_handle->before() =~ /Ticket (\d+) created/;
+my $merge_ticket_A = $1;
+ok($merge_ticket_A, "Got first ticket to merge id=$merge_ticket_A");
+expect_send("create -t ticket set subject='CLIMergeTest2-$$'", 'Creating second ticket to merge...');
+expect_like(qr/Ticket \d+ created/, 'Created second ticket');
+expect_handle->before() =~ /Ticket (\d+) created/;
+my $merge_ticket_B = $1;
+ok($merge_ticket_B, "Got second ticket to merge id=$merge_ticket_B");
+expect_send("merge $merge_ticket_B $merge_ticket_A", 'Merging the tickets...');
+expect_like(qr/Merge completed/, 'Merged the tickets');
+
+TODO: {
+ local $TODO = "we generate a spurious warning here";
+ $m->no_warnings_ok;
+}
+
+expect_send("show ticket/$merge_ticket_A/history", 'Checking merge on first ticket');
+expect_like(qr/Merged into ticket #$merge_ticket_A by root/, 'Merge recorded in first ticket');
+expect_send("show ticket/$merge_ticket_B/history", 'Checking merge on second ticket');
+expect_like(qr/Merged into ticket #$merge_ticket_A by root/, 'Merge recorded in second ticket');
+# }}}
+
+# {{{ test taking/stealing tickets
+{
+ # create a user; give them privileges to take and steal
+ ### TODO: implement 'grant' in the CLI tool; use that here instead.
+ ### this breaks the abstraction barrier, like, a lot.
+ my $steal_user = RT::User->new($RT::SystemUser);
+ my ($steal_user_id, $msg) = $steal_user->Create( Name => "fooser$$",
+ EmailAddress => "fooser$$\@localhost",
+ Privileged => 1,
+ Password => 'foobar',
+ );
+ ok($steal_user_id, "Created the user? $msg");
+ my $steal_queue = RT::Queue->new($RT::SystemUser);
+ my $steal_queue_id;
+ ($steal_queue_id, $msg) = $steal_queue->Create( Name => "Steal$$" );
+ ok($steal_queue_id, "Got the queue? $msg");
+ ok($steal_queue->id, "queue obj has id");
+ my $status;
+ ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'ShowTicket', Object => $steal_queue );
+ ok($status, "Gave 'ShowTicket' to our user? $msg");
+ ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'OwnTicket', Object => $steal_queue );
+ ok($status, "Gave 'OwnTicket' to our user? $msg");
+ ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'StealTicket', Object => $steal_queue );
+ ok($status, "Gave 'StealTicket' to our user? $msg");
+ ($status, $msg) = $steal_user->PrincipalObj->GrantRight( Right => 'TakeTicket', Object => $steal_queue );
+ ok($status, "Gave 'TakeTicket' to our user? $msg");
+
+ # create a ticket to take/steal
+ expect_send("create -t ticket set queue=$steal_queue_id subject='CLIStealTest-$$'", 'Creating ticket to steal...');
+ expect_like(qr/Ticket \d+ created/, 'Created ticket');
+ expect_handle->before() =~ /Ticket (\d+) created/;
+ my $steal_ticket_id = $1;
+ ok($steal_ticket_id, "Got ticket to steal id=$steal_ticket_id");
+
+ # root takes the ticket
+ expect_send("take $steal_ticket_id", 'root takes the ticket...');
+ expect_like(qr/Owner changed from Nobody to root/, 'root took the ticket');
+ expect_quit();
+
+ # log in as the non-root user
+ $ENV{'RTUSER'} = "fooser$$";
+ $ENV{'RTPASSWD'} = 'foobar';
+ expect_run( command => "$rt_tool_path shell", prompt => 'rt> ', quit => 'quit',);
+
+ # user tries to take the ticket, fails
+ # shouldn't be able to 'take' a ticket which someone else has taken out from
+ # under you; that should produce an error. should have to explicitly
+ # 'steal' it back from them. 'steal' can automatically 'take' a ticket,
+ # though.
+ expect_send("take $steal_ticket_id", 'user tries to take the ticket...');
+ expect_like(qr/You can only take tickets that are unowned/, '...and fails.');
+ expect_send("show ticket/$steal_ticket_id -f owner", 'Double-checking...');
+ expect_like(qr/Owner: root/, '...no change.');
+
+ # user steals the ticket
+ expect_send("steal $steal_ticket_id", 'user tries to *steal* the ticket...');
+ expect_like(qr/Owner changed from root to fooser$$/, '...and succeeds!');
+ expect_send("show ticket/$steal_ticket_id -f owner", 'Double-checking...');
+ expect_like(qr/Owner: fooser$$/, '...yup, it worked.');
+ expect_quit();
+
+ # log back in as root
+ $ENV{'RTUSER'} = 'root';
+ $ENV{'RTPASSWD'} = 'password';
+ expect_run( command => "$rt_tool_path shell", prompt => 'rt> ', quit => 'quit',);
+
+ # root steals the ticket back
+ expect_send("steal $steal_ticket_id", 'root steals the ticket back...');
+ expect_like(qr/Owner changed from fooser$$ to root/, '...and succeeds.');
+}
+# }}}
+
+# {{{ test ticket linking
+ my @link_relns = ( 'DependsOn', 'DependedOnBy', 'RefersTo', 'ReferredToBy',
+ 'MemberOf', 'HasMember', );
+ my %display_relns = map { $_ => $_ } @link_relns;
+ $display_relns{HasMember} = 'Members';
+
+ my $link1_id = ok_create_ticket( "LinkTicket1-$$" );
+ my $link2_id = ok_create_ticket( "LinkTicket2-$$" );
+
+ foreach my $reln (@link_relns) {
+ # create link
+ expect_send("link $link1_id $reln $link2_id", "Link by $reln...");
+ expect_like(qr/Created link $link1_id $reln $link2_id/, 'Linked');
+ expect_send("show -s ticket/$link1_id/links", "Checking creation of $reln...");
+ expect_like(qr/$display_relns{$reln}: [\w\d\.\-]+:\/\/[\w\d\.]+\/ticket\/$link2_id/, "Created link $reln");
+
+ # delete link
+ expect_send("link -d $link1_id $reln $link2_id", "Delete $reln...");
+ expect_like(qr/Deleted link $link1_id $reln $link2_id/, 'Deleted');
+ expect_send("show ticket/$link1_id/links", "Checking removal of $reln...");
+ ok( expect_handle->before() !~ /\Q$display_relns{$reln}: \E[\w\d\.\-]+:\/\/[w\d\.]+\/ticket\/$link2_id/, "Removed link $reln" );
+ #expect_unlike(qr/\Q$reln: \E[\w\d\.]+\Q://\E[w\d\.]+\/ticket\/$link2_id/, "Removed link $reln");
+
+ }
+# }}}
+
+expect_quit(); # We need to do this ourselves, so that we quit
+ # *before* we tear down the webserver.
+
+# helper function
+sub ok_create_ticket {
+ my $subject = shift;
+
+ expect_send("create -t ticket set subject='$subject'", 'Creating ticket...');
+ expect_like(qr/Ticket \d+ created/, "Created ticket '$subject'");
+ expect_handle->before() =~ /Ticket (\d+) created/;
+ my $id = $1;
+ ok($id, "Got ticket id=$id");
+
+ return $id;
+}
+
+# wrap up all the file handling stuff for attachment testing
+sub check_attachment {
+ my $attachment_path = shift;
+ (my $filename = $attachment_path) =~ s/.*\/(.*)$/$1/;
+ expect_send("comment -m 'attach file' -a $attachment_path $ticket_id", "Adding an attachment ($filename)");
+ expect_like(qr/Message recorded/, "Added the attachment");
+ expect_send("show ticket/$ticket_id/attachments","Finding Attachment");
+ my $attachment_regex = qr/(\d+):\s+$filename/;
+ expect_like($attachment_regex,"Attachment Uploaded");
+ expect_handle->before() =~ $attachment_regex;
+ my $attachment_id = $1;
+ expect_send("show ticket/$ticket_id/attachments/$attachment_id/content","Fetching Attachment");
+ open (my $fh, $attachment_path) or die "Can't open $attachment_path: $!";
+ my $attachment_content = do { local($/); <$fh> };
+ close $fh;
+ chomp $attachment_content;
+ expect_is($attachment_content,"Attachment contains original text");
+}
+
+
+
+1;
diff --git a/rt/t/web/command_line_with_unknown_field.t b/rt/t/web/command_line_with_unknown_field.t
new file mode 100644
index 000000000..9a7ec7acd
--- /dev/null
+++ b/rt/t/web/command_line_with_unknown_field.t
@@ -0,0 +1,34 @@
+#!/usr/bin/perl -w
+
+use strict;
+use File::Spec ();
+use Test::Expect;
+use RT::Test tests => 10;
+my ($baseurl, $m) = RT::Test->started_ok;
+my $rt_tool_path = "$RT::BinPath/rt";
+
+$ENV{'RTUSER'} = 'root';
+$ENV{'RTPASSWD'} = 'password';
+$RT::Logger->debug("Connecting to server at ".RT->Config->Get('WebBaseURL'));
+$ENV{'RTSERVER'} =RT->Config->Get('WebBaseURL') ;
+$ENV{'RTDEBUG'} = '1';
+
+expect_run(
+ command => "$rt_tool_path shell",
+ prompt => 'rt> ',
+ quit => 'quit',
+);
+expect_send(q{create -t ticket set subject='new ticket' add cc=foo@example.com}, "Creating a ticket...");
+expect_like(qr/Ticket \d+ created/, "Created the ticket");
+expect_handle->before() =~ /Ticket (\d+) created/;
+my $ticket_id = $1;
+
+expect_send("edit ticket/$ticket_id set marge=simpson", 'set unknown field');
+expect_like(qr/marge: Unknown field/, 'marge is unknown field');
+expect_like(qr/marge: simpson/, 'the value we set for marge is shown too');
+
+expect_send("edit ticket/$ticket_id set homer=simpson", 'set unknown field');
+expect_like(qr/homer: Unknown field/, 'homer is unknown field');
+expect_like(qr/homer: simpson/, 'the value we set for homer is shown too');
+
+expect_quit();
diff --git a/rt/t/web/compilation_errors.t b/rt/t/web/compilation_errors.t
new file mode 100644
index 000000000..46a862868
--- /dev/null
+++ b/rt/t/web/compilation_errors.t
@@ -0,0 +1,68 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More;
+use File::Find;
+BEGIN {
+ sub wanted {
+ -f && /\.html$/ && $_ !~ /Logout.html$/;
+ }
+ my $tests = 7;
+ find( sub { wanted() and $tests += 4 }, 'share/html/' );
+ plan tests => $tests;
+}
+
+
+use HTTP::Request::Common;
+use HTTP::Cookies;
+use LWP;
+use Encode;
+
+my $cookie_jar = HTTP::Cookies->new;
+
+use RT::Test;
+my ($baseurl, $agent) = RT::Test->started_ok;
+
+# give the agent a place to stash the cookies
+$agent->cookie_jar($cookie_jar);
+
+# get the top page
+my $url = $agent->rt_base_url;
+diag "Base URL is '$url'" if $ENV{TEST_VERBOSE};
+$agent->get($url);
+
+is ($agent->{'status'}, 200, "Loaded a page");
+
+# {{{ test a login
+
+# follow the link marked "Login"
+
+ok($agent->{form}->find_input('user'));
+
+ok($agent->{form}->find_input('pass'));
+like ($agent->{'content'} , qr/username:/i);
+$agent->field( 'user' => 'root' );
+$agent->field( 'pass' => 'password' );
+# the field isn't named, so we have to click link 0
+$agent->click(0);
+is($agent->{'status'}, 200, "Fetched the page ok");
+like( $agent->{'content'} , qr/Logout/i, "Found a logout link");
+
+
+find ( sub { wanted() and test_get($File::Find::name) } , 'share/html/');
+
+sub test_get {
+ my $file = shift;
+
+ $file =~ s#^share/html/##;
+ diag( "testing $url/$file" ) if $ENV{TEST_VERBOSE};
+ ok ($agent->get("$url/$file", "GET $url/$file"), "Can Get $url/$file");
+ is ($agent->{'status'}, 200, "Loaded $file");
+# ok( $agent->{'content'} =~ /Logout/i, "Found a logout link on $file ");
+ ok( $agent->{'content'} !~ /Not logged in/i, "Still logged in for $file");
+ ok( $agent->{'content'} !~ /raw error/i, "Didn't get a Mason compilation error on $file");
+}
+
+# }}}
+
+1;
diff --git a/rt/t/web/config_tab_right.t b/rt/t/web/config_tab_right.t
new file mode 100644
index 000000000..4dc9ec082
--- /dev/null
+++ b/rt/t/web/config_tab_right.t
@@ -0,0 +1,41 @@
+#!/usr/bin/perl -w
+use strict;
+use warnings;
+
+use RT::Test tests => 8;
+
+my ($uname, $upass, $user) = ('tester', 'tester');
+{
+ $user = RT::User->new($RT::SystemUser);
+ my ($status, $msg) = $user->Create(
+ Name => $uname,
+ Password => $upass,
+ Disabled => 0,
+ Privileged => 1,
+ );
+ ok($status, 'created a user');
+}
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login($uname, $upass), "logged in";
+
+{
+ $m->content_unlike(qr/Configuration/, 'no configuration');
+ $m->get('/Admin/');
+ is $m->status, 403, 'no access to /Admin/';
+}
+
+RT::Test->set_rights(
+ { Principal => $user->PrincipalObj,
+ Right => [qw(ShowConfigTab)],
+ },
+);
+
+{
+ $m->get('/');
+ $m->content_like(qr/Configuration/, 'configuration is there');
+
+ $m->follow_link_ok({text => 'Configuration'});
+ is $m->status, 200, 'user has access to /Admin/';
+}
+
diff --git a/rt/t/web/crypt-gnupg.t b/rt/t/web/crypt-gnupg.t
new file mode 100644
index 000000000..fb28c887c
--- /dev/null
+++ b/rt/t/web/crypt-gnupg.t
@@ -0,0 +1,446 @@
+#!/usr/bin/perl -w
+use strict;
+
+use RT::Test tests => 94;
+
+plan skip_all => 'GnuPG required.'
+ unless eval 'use GnuPG::Interface; 1';
+plan skip_all => 'gpg executable is required.'
+ unless RT::Test->find_executable('gpg');
+
+
+use RT::Action::SendEmail;
+
+eval 'use GnuPG::Interface; 1' or plan skip_all => 'GnuPG required.';
+
+RT::Test->set_mail_catcher;
+
+RT->Config->Set( CommentAddress => 'general@example.com');
+RT->Config->Set( CorrespondAddress => 'general@example.com');
+RT->Config->Set( DefaultSearchResultFormat => qq{
+ '<B><A HREF="__WebPath__/Ticket/Display.html?id=__id__">__id__</a></B>/TITLE:#',
+ '<B><A HREF="__WebPath__/Ticket/Display.html?id=__id__">__Subject__</a></B>/TITLE:Subject',
+ 'OO-__OwnerName__-O',
+ 'OR-__Requestors__-O',
+ 'KO-__KeyOwnerName__-K',
+ 'KR-__KeyRequestors__-K',
+ Status});
+
+use File::Spec ();
+use Cwd;
+use File::Temp qw(tempdir);
+my $homedir = tempdir( CLEANUP => 1 );
+
+use_ok('RT::Crypt::GnuPG');
+
+RT->Config->Set( 'GnuPG',
+ Enable => 1,
+ OutgoingMessagesFormat => 'RFC' );
+
+RT->Config->Set( 'GnuPGOptions',
+ homedir => $homedir,
+ passphrase => 'recipient',
+ 'no-permission-warning' => undef,
+ 'trust-model' => 'always');
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::GnuPG' );
+
+RT::Test->import_gnupg_key('recipient@example.com', 'public');
+RT::Test->import_gnupg_key('recipient@example.com', 'secret');
+RT::Test->import_gnupg_key('general@example.com', 'public');
+RT::Test->import_gnupg_key('general@example.com', 'secret');
+RT::Test->import_gnupg_key('general@example.com.2', 'public');
+RT::Test->import_gnupg_key('general@example.com.2', 'secret');
+
+ok(my $user = RT::User->new($RT::SystemUser));
+ok($user->Load('root'), "Loaded user 'root'");
+$user->SetEmailAddress('recipient@example.com');
+
+my $queue = RT::Test->load_or_create_queue(
+ Name => 'General',
+ CorrespondAddress => 'general@example.com',
+);
+ok $queue && $queue->id, 'loaded or created queue';
+my $qid = $queue->id;
+
+RT::Test->set_rights(
+ Principal => 'Everyone',
+ Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ModifyTicket'],
+);
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'logged in';
+
+$m->get_ok("/Admin/Queues/Modify.html?id=$qid");
+$m->form_with_fields('Sign', 'Encrypt');
+$m->field(Encrypt => 1);
+$m->submit;
+
+RT::Test->clean_caught_mails;
+
+$m->goto_create_ticket( $queue );
+$m->form_name('TicketCreate');
+$m->field('Subject', 'Encryption test');
+$m->field('Content', 'Some content');
+ok($m->value('Encrypt', 2), "encrypt tick box is checked");
+ok(!$m->value('Sign', 2), "sign tick box is unchecked");
+$m->submit;
+is($m->status, 200, "request successful");
+
+$m->get($baseurl); # ensure that the mail has been processed
+
+my @mail = RT::Test->fetch_caught_mails;
+ok(@mail, "got some mail");
+
+$user->SetEmailAddress('general@example.com');
+for my $mail (@mail) {
+ unlike $mail, qr/Some content/, "outgoing mail was encrypted";
+
+ my ($content_type) = $mail =~ /^(Content-Type: .*)/m;
+ my ($mime_version) = $mail =~ /^(MIME-Version: .*)/m;
+ my $body = strip_headers($mail);
+
+ $mail = << "MAIL";
+Subject: RT mail sent back into RT
+From: general\@example.com
+To: recipient\@example.com
+$mime_version
+$content_type
+
+$body
+MAIL
+
+ 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");
+
+ is ($tick->Subject,
+ "RT mail sent back into RT",
+ "Correct subject"
+ );
+
+ my $txn = $tick->Transactions->First;
+ my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
+
+ 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($attachments[0]->Content, qr/Some content/, "RT's mail includes copy of ticket text");
+ like($attachments[0]->Content, qr/$RT::rtname/, "RT's mail includes this instance's name");
+}
+
+$m->get("$baseurl/Admin/Queues/Modify.html?id=$qid");
+$m->form_with_fields('Sign', 'Encrypt');
+$m->field(Encrypt => undef);
+$m->field(Sign => 1);
+$m->submit;
+
+RT::Test->clean_caught_mails;
+
+$m->goto_create_ticket( $queue );
+$m->form_name('TicketCreate');
+$m->field('Subject', 'Signing test');
+$m->field('Content', 'Some other content');
+ok(!$m->value('Encrypt', 2), "encrypt tick box is unchecked");
+ok($m->value('Sign', 2), "sign tick box is checked");
+$m->submit;
+is($m->status, 200, "request successful");
+
+$m->get($baseurl); # ensure that the mail has been processed
+
+@mail = RT::Test->fetch_caught_mails;
+ok(@mail, "got some mail");
+for my $mail (@mail) {
+ like $mail, qr/Some other content/, "outgoing mail was not encrypted";
+ like $mail, qr/-----BEGIN PGP SIGNATURE-----[\s\S]+-----END PGP SIGNATURE-----/, "data has some kind of signature";
+
+ my ($content_type) = $mail =~ /^(Content-Type: .*)/m;
+ my ($mime_version) = $mail =~ /^(MIME-Version: .*)/m;
+ my $body = strip_headers($mail);
+
+ $mail = << "MAIL";
+Subject: More RT mail sent back into RT
+From: general\@example.com
+To: recipient\@example.com
+$mime_version
+$content_type
+
+$body
+MAIL
+
+ 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");
+
+ is ($tick->Subject,
+ "More RT mail sent back into RT",
+ "Correct subject"
+ );
+
+ my $txn = $tick->Transactions->First;
+ my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
+
+ 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 unencrypted"
+ );
+ is( $msg->GetHeader('X-RT-Incoming-Signature'),
+ 'general <general@example.com>',
+ "RT's outgoing mail looks signed"
+ );
+
+ like($attachments[0]->Content, qr/Some other content/, "RT's mail includes copy of ticket text");
+ like($attachments[0]->Content, qr/$RT::rtname/, "RT's mail includes this instance's name");
+}
+
+$m->get("$baseurl/Admin/Queues/Modify.html?id=$qid");
+$m->form_with_fields('Sign', 'Encrypt');
+$m->field(Encrypt => 1);
+$m->field(Sign => 1);
+$m->submit;
+
+RT::Test->clean_caught_mails;
+
+
+$m->goto_create_ticket( $queue );
+$m->form_name('TicketCreate');
+$m->field('Subject', 'Crypt+Sign test');
+$m->field('Content', 'Some final? content');
+ok($m->value('Encrypt', 2), "encrypt tick box is checked");
+ok($m->value('Sign', 2), "sign tick box is checked");
+$m->submit;
+is($m->status, 200, "request successful");
+
+$m->get($baseurl); # ensure that the mail has been processed
+
+@mail = RT::Test->fetch_caught_mails;
+ok(@mail, "got some mail");
+for my $mail (@mail) {
+ unlike $mail, qr/Some other content/, "outgoing mail was encrypted";
+
+ my ($content_type) = $mail =~ /^(Content-Type: .*)/m;
+ my ($mime_version) = $mail =~ /^(MIME-Version: .*)/m;
+ my $body = strip_headers($mail);
+
+ $mail = << "MAIL";
+Subject: Final RT mail sent back into RT
+From: general\@example.com
+To: recipient\@example.com
+$mime_version
+$content_type
+
+$body
+MAIL
+
+ 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");
+
+ is ($tick->Subject,
+ "Final RT mail sent back into RT",
+ "Correct subject"
+ );
+
+ my $txn = $tick->Transactions->First;
+ my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
+
+ 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"
+ );
+ is( $msg->GetHeader('X-RT-Incoming-Signature'),
+ 'general <general@example.com>',
+ "RT's outgoing mail looks signed"
+ );
+
+ like($attachments[0]->Content, qr/Some final\? content/, "RT's mail includes copy of ticket text");
+ like($attachments[0]->Content, qr/$RT::rtname/, "RT's mail includes this instance's name");
+}
+
+RT::Test->clean_caught_mails;
+
+$m->goto_create_ticket( $queue );
+$m->form_name('TicketCreate');
+$m->field('Subject', 'Test crypt-off on encrypted queue');
+$m->field('Content', 'Thought you had me figured out didya');
+$m->field(Encrypt => undef, 2); # turn off encryption
+ok(!$m->value('Encrypt', 2), "encrypt tick box is now unchecked");
+ok($m->value('Sign', 2), "sign tick box is still checked");
+$m->submit;
+is($m->status, 200, "request successful");
+
+$m->get($baseurl); # ensure that the mail has been processed
+
+@mail = RT::Test->fetch_caught_mails;
+ok(@mail, "got some mail");
+for my $mail (@mail) {
+ like $mail, qr/Thought you had me figured out didya/, "outgoing mail was unencrypted";
+
+ my ($content_type) = $mail =~ /^(Content-Type: .*)/m;
+ my ($mime_version) = $mail =~ /^(MIME-Version: .*)/m;
+ my $body = strip_headers($mail);
+
+ $mail = << "MAIL";
+Subject: Post-final! RT mail sent back into RT
+From: general\@example.com
+To: recipient\@example.com
+$mime_version
+$content_type
+
+$body
+MAIL
+
+ 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");
+
+ is ($tick->Subject,
+ "Post-final! RT mail sent back into RT",
+ "Correct subject"
+ );
+
+ my $txn = $tick->Transactions->First;
+ my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
+
+ 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 unencrypted"
+ );
+ is( $msg->GetHeader('X-RT-Incoming-Signature'),
+ 'general <general@example.com>',
+ "RT's outgoing mail looks signed"
+ );
+
+ like($attachments[0]->Content, qr/Thought you had me figured out didya/, "RT's mail includes copy of ticket text");
+ like($attachments[0]->Content, qr/$RT::rtname/, "RT's mail includes this instance's name");
+}
+
+sub strip_headers
+{
+ my $mail = shift;
+ $mail =~ s/.*?\n\n//s;
+ return $mail;
+}
+
+# now test the OwnerNameKey and RequestorsKey fields
+
+my $nokey = RT::Test->load_or_create_user(Name => 'nokey', EmailAddress => 'nokey@example.com');
+$nokey->PrincipalObj->GrantRight(Right => 'CreateTicket');
+$nokey->PrincipalObj->GrantRight(Right => 'OwnTicket');
+
+my $tick = RT::Ticket->new( $RT::SystemUser );
+$tick->Create(Subject => 'owner lacks pubkey', Queue => 'general',
+ Owner => $nokey);
+ok(my $id = $tick->id, 'created ticket for owner-without-pubkey');
+
+$tick = RT::Ticket->new( $RT::SystemUser );
+$tick->Create(Subject => 'owner has pubkey', Queue => 'general',
+ Owner => 'root');
+ok($id = $tick->id, 'created ticket for owner-with-pubkey');
+
+my $mail = << "MAIL";
+Subject: Nokey requestor
+From: nokey\@example.com
+To: general\@example.com
+
+hello
+MAIL
+
+((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");
+
+$tick = RT::Ticket->new( $RT::SystemUser );
+$tick->Load( $id );
+ok ($tick->id, "loaded ticket #$id");
+
+is ($tick->Subject,
+ "Nokey requestor",
+ "Correct subject"
+);
+
+# test key selection
+my $key1 = "EC1E81E7DC3DB42788FB0E4E9FA662C06DE22FC2";
+my $key2 = "75E156271DCCF02DDD4A7A8CDF651FA0632C4F50";
+
+ok($user = RT::User->new($RT::SystemUser));
+ok($user->Load('root'), "Loaded user 'root'");
+is($user->PreferredKey, $key1, "preferred key is set correctly");
+$m->get("$baseurl/Prefs/Other.html");
+like($m->content, qr/Preferred key/, "preferred key option shows up in preference");
+
+# XXX: mech doesn't let us see the current value of the select, apparently
+like($m->content, qr/$key1/, "first key shows up in preferences");
+like($m->content, qr/$key2/, "second key shows up in preferences");
+like($m->content, qr/$key1.*?$key2/s, "first key shows up before the second");
+
+$m->form_number(3);
+$m->select("PreferredKey" => $key2);
+$m->submit;
+
+ok($user = RT::User->new($RT::SystemUser));
+ok($user->Load('root'), "Loaded user 'root'");
+is($user->PreferredKey, $key2, "preferred key is set correctly to the new value");
+
+$m->get("$baseurl/Prefs/Other.html");
+like($m->content, qr/Preferred key/, "preferred key option shows up in preference");
+
+# XXX: mech doesn't let us see the current value of the select, apparently
+like($m->content, qr/$key2/, "second key shows up in preferences");
+like($m->content, qr/$key1/, "first key shows up in preferences");
+like($m->content, qr/$key2.*?$key1/s, "second key (now preferred) shows up before the first");
+
+# test that the new fields work
+$m->get("$baseurl/Search/Simple.html?q=General");
+my $content = $m->content;
+$content =~ s/&#40;/(/g;
+$content =~ s/&#41;/)/g;
+
+like($content, qr/OO-Nobody-O/, "original OwnerName untouched");
+like($content, qr/OO-nokey-O/, "original OwnerName untouched");
+like($content, qr/OO-root-O/, "original OwnerName untouched");
+
+like($content, qr/OR-recipient\@example.com-O/, "original Requestors untouched");
+like($content, qr/OR-nokey\@example.com-O/, "original Requestors untouched");
+
+like($content, qr/KO-root-K/, "KeyOwnerName does not issue no-pubkey warning for recipient");
+like($content, qr/KO-nokey \(no pubkey!\)-K/, "KeyOwnerName issues no-pubkey warning for root");
+like($content, qr/KO-Nobody \(no pubkey!\)-K/, "KeyOwnerName issues no-pubkey warning for nobody");
+
+like($content, qr/KR-recipient\@example.com-K/, "KeyRequestors does not issue no-pubkey warning for recipient\@example.com");
+like($content, qr/KR-general\@example.com-K/, "KeyRequestors does not issue no-pubkey warning for general\@example.com");
+like($content, qr/KR-nokey\@example.com \(no pubkey!\)-K/, "KeyRequestors DOES issue no-pubkey warning for nokey\@example.com");
+
diff --git a/rt/t/web/custom_frontpage.t b/rt/t/web/custom_frontpage.t
new file mode 100644
index 000000000..45a390ab0
--- /dev/null
+++ b/rt/t/web/custom_frontpage.t
@@ -0,0 +1,61 @@
+#!/usr/bin/perl -w
+use strict;
+
+use RT::Test tests => 7;
+my ($baseurl, $m) = RT::Test->started_ok;
+
+my $url = $m->rt_base_url;
+
+my $user_obj = RT::User->new($RT::SystemUser);
+my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('customer@example.com');
+ok($ret, 'ACL test user creation');
+$user_obj->SetName('customer');
+$user_obj->SetPrivileged(1);
+($ret, $msg) = $user_obj->SetPassword('customer');
+$user_obj->PrincipalObj->GrantRight(Right => 'LoadSavedSearch');
+$user_obj->PrincipalObj->GrantRight(Right => 'EditSavedSearch');
+$user_obj->PrincipalObj->GrantRight(Right => 'CreateSavedSearch');
+$user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf');
+
+ok $m->login( customer => 'customer' ), "logged in";
+
+$m->get ( $url."Search/Build.html");
+
+#create a saved search
+$m->form_name ('BuildQuery');
+
+$m->field ( "ValueOfAttachment" => 'stupid');
+$m->field ( "SavedSearchDescription" => 'stupid tickets');
+$m->click_button (name => 'SavedSearchSave');
+
+$m->get ( $url.'Prefs/MyRT.html' );
+$m->content_like (qr/stupid tickets/, 'saved search listed in rt at a glance items');
+
+ok $m->login, 'we did log in as root';
+
+$m->get ( $url.'Prefs/MyRT.html' );
+$m->form_name ('SelectionBox-body');
+# can't use submit form for mutli-valued select as it uses set_fields
+$m->field ('body-Selected' => ['component-QuickCreate', 'system-Unowned Tickets', 'system-My Tickets']);
+$m->click_button (name => 'remove');
+$m->form_name ('SelectionBox-body');
+#$m->click_button (name => 'body-Save');
+$m->get ( $url );
+$m->content_lacks ('highest priority tickets', 'remove everything from body pane');
+
+$m->get ( $url.'Prefs/MyRT.html' );
+$m->form_name ('SelectionBox-body');
+$m->field ('body-Available' => ['component-QuickCreate', 'system-Unowned Tickets', 'system-My Tickets']);
+$m->click_button (name => 'add');
+
+$m->form_name ('SelectionBox-body');
+$m->field ('body-Selected' => ['component-QuickCreate']);
+$m->click_button (name => 'movedown');
+
+$m->form_name ('SelectionBox-body');
+$m->click_button (name => 'movedown');
+
+$m->form_name ('SelectionBox-body');
+#$m->click_button (name => 'body-Save');
+$m->get ( $url );
+$m->content_like (qr'highest priority tickets', 'adds them back');
diff --git a/rt/t/web/custom_search.t b/rt/t/web/custom_search.t
new file mode 100644
index 000000000..05cfdab60
--- /dev/null
+++ b/rt/t/web/custom_search.t
@@ -0,0 +1,84 @@
+#!/usr/bin/perl -w
+use strict;
+
+use RT::Test tests => 11;
+my ($baseurl, $m) = RT::Test->started_ok;
+my $url = $m->rt_base_url;
+
+# reset preferences for easier test?
+
+
+
+my $t = RT::Ticket->new($RT::SystemUser);
+$t->Create(Subject => 'for custom search'.$$, Queue => 'general',
+ Owner => 'root', Requestor => 'customsearch@localhost');
+ok(my $id = $t->id, 'created ticket for custom search');
+
+ok $m->login, 'logged in';
+
+my $t_link = $m->find_link( text => "for custom search".$$ );
+like ($t_link->url, qr/$id/, 'link to the ticket we created');
+
+$m->content_lacks ('customsearch@localhost', 'requestor not displayed ');
+$m->get ( $url.'Prefs/MyRT.html' );
+my $cus_hp = $m->find_link( text => "My Tickets" );
+my $cus_qs = $m->find_link( text => "Quick search" );
+$m->get ($cus_hp);
+$m->content_like (qr'highest priority tickets');
+
+# add Requestor to the fields
+$m->form_name ('BuildQuery');
+# can't use submit form for mutli-valued select as it uses set_fields
+$m->field (SelectDisplayColumns => ['Requestors']);
+$m->click_button (name => 'AddCol') ;
+
+$m->form_name ('BuildQuery');
+$m->click_button (name => 'Save');
+
+$m->get( $url );
+$m->content_contains ('customsearch@localhost', 'requestor now displayed ');
+
+
+# now remove Requestor from the fields
+$m->get ($cus_hp);
+
+$m->form_name ('BuildQuery');
+
+my $cdc = $m->current_form->find_input('CurrentDisplayColumns');
+my ($requestor_value) = grep { /Requestor/ } $cdc->possible_values;
+ok($requestor_value, "got the requestor value");
+
+$m->field (CurrentDisplayColumns => $requestor_value);
+$m->click_button (name => 'RemoveCol') ;
+
+$m->form_name ('BuildQuery');
+$m->click_button (name => 'Save');
+
+$m->get( $url );
+$m->content_lacks ('customsearch@localhost', 'requestor not displayed ');
+
+
+# try to disable General from quick search
+
+# Note that there's a small problem in the current implementation,
+# since ticked quese are wanted, we do the invesrsion. So any
+# queue added during the quicksearch setting will be unticked.
+my $nlinks = $#{$m->find_all_links( text => "General" )};
+$m->get ($cus_qs);
+$m->form_name ('Preferences');
+$m->untick('Want-General', '1');
+$m->click_button (name => 'Save');
+
+$m->get( $url );
+is ($#{$m->find_all_links( text => "General" )}, $nlinks - 1,
+ 'General gone from quicksearch list');
+
+# get it back
+$m->get ($cus_qs);
+$m->form_name ('Preferences');
+$m->tick('Want-General', '1');
+$m->click_button (name => 'Save');
+
+$m->get( $url );
+is ($#{$m->find_all_links( text => "General" )}, $nlinks,
+ 'General back in quicksearch list');
diff --git a/rt/t/web/dashboard_with_deleted_saved_search.t b/rt/t/web/dashboard_with_deleted_saved_search.t
new file mode 100644
index 000000000..328095aaf
--- /dev/null
+++ b/rt/t/web/dashboard_with_deleted_saved_search.t
@@ -0,0 +1,89 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 18;
+my ( $url, $m ) = RT::Test->started_ok;
+ok( $m->login, 'logged in' );
+
+# create a saved search
+$m->get_ok( $url . "/Search/Build.html?Query=" . 'id=1' );
+
+$m->submit_form(
+ form_name => 'BuildQuery',
+ fields => { SavedSearchDescription => 'foo', },
+ button => 'SavedSearchSave',
+);
+
+my ( $search_uri, $user_id, $search_id ) =
+ $m->content =~ /value="(RT::User-(\d+)-SavedSearch-(\d+))"/;
+$m->submit_form(
+ form_name => 'BuildQuery',
+ fields => { SavedSearchLoad => $search_uri },
+ button => 'SavedSearchSave',
+);
+
+$m->content_like( qr/name="SavedSearchDelete"\s+value="Delete"/,
+ 'found Delete button' );
+$m->content_like(
+ qr/name="SavedSearchDescription"\s+value="foo"/,
+ 'found Description input with the value filled'
+);
+
+# create a dashboard with the created search
+
+$m->get_ok( $url . "/Dashboards/Modify.html?Create=1" );
+$m->submit_form(
+ form_name => 'ModifyDashboard',
+ fields => { Name => 'bar' },
+);
+
+$m->content_like( qr/Saved dashboard bar/i, 'dashboard saved' );
+my $dashboard_queries_link = $m->find_link( text_regex => qr/Queries/ );
+my ( $dashboard_id ) = $dashboard_queries_link->url =~ /id=(\d+)/;
+
+$m->get_ok( $url . "/Dashboards/Queries.html?id=$dashboard_id" );
+
+$m->content_lacks( 'value="Update"', 'no update button' );
+
+$m->submit_form(
+ form_name => 'Dashboard-Searches-body',
+ fields =>
+ { 'Searches-body-Available' => "search-$search_id-RT::User-$user_id" },
+ button => 'add',
+);
+
+$m->content_like( qr/Dashboard updated/i, 'added search foo to dashboard bar' );
+
+# delete the created search
+
+$m->get_ok( $url . "/Search/Build.html?Query=" . 'id=1' );
+$m->submit_form(
+ form_name => 'BuildQuery',
+ fields => { SavedSearchLoad => $search_uri },
+);
+$m->submit_form(
+ form_name => 'BuildQuery',
+ button => 'SavedSearchDelete',
+);
+
+$m->content_lacks( $search_uri, 'deleted search foo' );
+
+# here is what we really want to test
+
+$m->get_ok( $url . "/Dashboards/Queries.html?id=$dashboard_id" );
+$m->content_like( qr/Deleted queries/i, 'found deleted message' );
+
+# Update button shows so we can update the deleted search easily
+$m->content_contains( 'value="Update"', 'found update button' );
+
+$m->submit_form(
+ form_name => 'Dashboard-Searches-body',
+ button => 'update',
+);
+
+$m->content_unlike( qr/Deleted queries/i, 'deleted message is gone' );
+$m->content_lacks( 'value="Update"', 'update button is gone too' );
+
+$m->get_warnings; # we'll get a lot of warnings because the deleted search
+
diff --git a/rt/t/web/dashboards-groups.t b/rt/t/web/dashboards-groups.t
new file mode 100644
index 000000000..cbf1d6a9f
--- /dev/null
+++ b/rt/t/web/dashboards-groups.t
@@ -0,0 +1,102 @@
+#!/usr/bin/perl -w
+use strict;
+
+use RT::Test tests => 40;
+my ($baseurl, $m) = RT::Test->started_ok;
+
+my $url = $m->rt_base_url;
+
+# create user and queue {{{
+my $user_obj = RT::User->new($RT::SystemUser);
+my ($ok, $msg) = $user_obj->LoadOrCreateByEmail('customer@example.com');
+ok($ok, 'ACL test user creation');
+$user_obj->SetName('customer');
+$user_obj->SetPrivileged(1);
+($ok, $msg) = $user_obj->SetPassword('customer');
+$user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf');
+my $currentuser = RT::CurrentUser->new($user_obj);
+
+my $queue = RT::Queue->new($RT::SystemUser);
+$queue->Create(Name => 'SearchQueue'.$$);
+
+$user_obj->PrincipalObj->GrantRight(Right => $_, Object => $queue)
+ for qw/SeeQueue ShowTicket OwnTicket/;
+
+# grant the user all these rights so we can make sure that the group rights
+# are checked and not these as well
+$user_obj->PrincipalObj->GrantRight(Right => $_, Object => $RT::System)
+ for qw/SubscribeDashboard CreateOwnDashboard SeeOwnDashboard ModifyOwnDashboard DeleteOwnDashboard/;
+# }}}
+# create and test groups (outer < inner < user) {{{
+my $inner_group = RT::Group->new($RT::SystemUser);
+($ok, $msg) = $inner_group->CreateUserDefinedGroup(Name => "inner", Description => "inner group");
+ok($ok, "created inner group: $msg");
+
+my $outer_group = RT::Group->new($RT::SystemUser);
+($ok, $msg) = $outer_group->CreateUserDefinedGroup(Name => "outer", Description => "outer group");
+ok($ok, "created outer group: $msg");
+
+($ok, $msg) = $outer_group->AddMember($inner_group->PrincipalId);
+ok($ok, "added inner as a member of outer: $msg");
+
+($ok, $msg) = $inner_group->AddMember($user_obj->PrincipalId);
+ok($ok, "added user as a member of member: $msg");
+
+ok($outer_group->HasMember($inner_group->PrincipalId), "outer has inner");
+ok(!$outer_group->HasMember($user_obj->PrincipalId), "outer doesn't have user directly");
+ok($outer_group->HasMemberRecursively($inner_group->PrincipalId), "outer has inner recursively");
+ok($outer_group->HasMemberRecursively($user_obj->PrincipalId), "outer has user recursively");
+
+ok(!$inner_group->HasMember($outer_group->PrincipalId), "inner doesn't have outer");
+ok($inner_group->HasMember($user_obj->PrincipalId), "inner has user");
+ok(!$inner_group->HasMemberRecursively($outer_group->PrincipalId), "inner doesn't have outer, even recursively");
+ok($inner_group->HasMemberRecursively($user_obj->PrincipalId), "inner has user recursively");
+# }}}
+
+ok $m->login(customer => 'customer'), "logged in";
+
+$m->get_ok("$url/Dashboards");
+
+$m->follow_link_ok({text => "New"});
+$m->form_name('ModifyDashboard');
+is_deeply([$m->current_form->find_input('Privacy')->possible_values], ["RT::User-" . $user_obj->Id], "the only selectable privacy is user");
+$m->content_lacks('Delete', "Delete button hidden because we are creating");
+
+$user_obj->PrincipalObj->GrantRight(Right => 'CreateGroupDashboard', Object => $inner_group);
+
+$m->follow_link_ok({text => "New"});
+$m->form_name('ModifyDashboard');
+is_deeply([$m->current_form->find_input('Privacy')->possible_values], ["RT::User-" . $user_obj->Id, "RT::Group-" . $inner_group->Id], "the only selectable privacies are user and inner group (not outer group)");
+$m->field("Name" => 'inner dashboard');
+$m->field("Privacy" => "RT::Group-" . $inner_group->Id);
+$m->content_lacks('Delete', "Delete button hidden because we are creating");
+
+$m->click_button(value => 'Create');
+$m->content_lacks("No permission to create dashboards");
+$m->content_contains("Saved dashboard inner dashboard");
+$m->content_lacks('Delete', "Delete button hidden because we lack DeleteDashboard");
+
+my $dashboard = RT::Dashboard->new($currentuser);
+my ($id) = $m->content =~ /name="id" value="(\d+)"/;
+ok($id, "got an ID, $id");
+$dashboard->LoadById($id);
+is($dashboard->Name, "inner dashboard");
+
+is($dashboard->Privacy, 'RT::Group-' . $inner_group->Id, "correct privacy");
+is($dashboard->PossibleHiddenSearches, 0, "all searches are visible");
+
+$m->no_warnings_ok;
+
+$m->get_ok("/Dashboards/Modify.html?id=$id");
+$m->content_lacks("inner dashboard", "no SeeGroupDashboard right");
+$m->content_contains("Permission denied");
+
+$m->warning_like(qr/Permission denied/, "got a permission denied warning");
+
+$user_obj->PrincipalObj->GrantRight(Right => 'SeeGroupDashboard', Object => $inner_group);
+$m->get_ok("/Dashboards/Modify.html?id=$id");
+$m->content_contains("inner dashboard", "we now have SeeGroupDashboard right");
+$m->content_lacks("Permission denied");
+
+$m->content_contains('Subscription', "Subscription link not hidden because we have SubscribeDashboard");
+
diff --git a/rt/t/web/dashboards-permissions.t b/rt/t/web/dashboards-permissions.t
new file mode 100644
index 000000000..172404289
--- /dev/null
+++ b/rt/t/web/dashboards-permissions.t
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use RT::Test tests => 7;
+my ($baseurl, $m) = RT::Test->started_ok;
+
+my $url = $m->rt_base_url;
+
+# create user and queue {{{
+my $user_obj = RT::User->new($RT::SystemUser);
+my ($ok, $msg) = $user_obj->LoadOrCreateByEmail('customer@example.com');
+ok($ok, 'ACL test user creation');
+$user_obj->SetName('customer');
+$user_obj->SetPrivileged(1);
+($ok, $msg) = $user_obj->SetPassword('customer');
+$user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf');
+my $currentuser = RT::CurrentUser->new($user_obj);
+
+my $queue = RT::Queue->new($RT::SystemUser);
+$queue->Create(Name => 'SearchQueue'.$$);
+
+$user_obj->PrincipalObj->GrantRight(Right => $_, Object => $queue)
+ for qw/SeeQueue ShowTicket OwnTicket/;
+
+$user_obj->PrincipalObj->GrantRight(Right => $_, Object => $RT::System)
+ for qw/SubscribeDashboard CreateOwnDashboard SeeOwnDashboard ModifyOwnDashboard DeleteOwnDashboard/;
+# }}}
+
+ok $m->login(customer => 'customer'), "logged in";
+
+$m->get_ok("$url/Dashboards");
+
+$m->follow_link_ok({text => "New"});
+$m->form_name('ModifyDashboard');
+is_deeply([$m->current_form->find_input('Privacy')->possible_values], ["RT::User-" . $user_obj->Id], "the only selectable privacy is user");
+$m->content_lacks('Delete', "Delete button hidden because we are creating");
+
diff --git a/rt/t/web/dashboards.t b/rt/t/web/dashboards.t
new file mode 100644
index 000000000..9d98ce6e4
--- /dev/null
+++ b/rt/t/web/dashboards.t
@@ -0,0 +1,250 @@
+#!/usr/bin/perl -w
+use strict;
+
+use RT::Test tests => 109;
+my ($baseurl, $m) = RT::Test->started_ok;
+
+my $url = $m->rt_base_url;
+
+my $user_obj = RT::User->new($RT::SystemUser);
+my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('customer@example.com');
+ok($ret, 'ACL test user creation');
+$user_obj->SetName('customer');
+$user_obj->SetPrivileged(1);
+($ret, $msg) = $user_obj->SetPassword('customer');
+$user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf');
+my $currentuser = RT::CurrentUser->new($user_obj);
+
+my $onlooker = RT::User->new($RT::SystemUser);
+($ret, $msg) = $onlooker->LoadOrCreateByEmail('onlooker@example.com');
+ok($ret, 'ACL test user creation');
+$onlooker->SetName('onlooker');
+$onlooker->SetPrivileged(1);
+($ret, $msg) = $onlooker->SetPassword('onlooker');
+
+my $queue = RT::Queue->new($RT::SystemUser);
+$queue->Create(Name => 'SearchQueue'.$$);
+
+for my $user ($user_obj, $onlooker) {
+ $user->PrincipalObj->GrantRight(Right => 'ModifySelf');
+ for my $right (qw/SeeQueue ShowTicket OwnTicket/) {
+ $user->PrincipalObj->GrantRight(Right => $right, Object => $queue);
+ }
+}
+
+ok $m->login(customer => 'customer'), "logged in";
+
+$m->get_ok($url."Dashboards/index.html");
+$m->content_lacks('<a href="/Dashboards/Modify.html?Create=1">New</a>',
+ "No 'new dashboard' link because we have no CreateOwnDashboard");
+
+$m->no_warnings_ok;
+
+$m->get_ok($url."Dashboards/Modify.html?Create=1");
+$m->content_contains("Permission denied");
+$m->content_lacks("Save Changes");
+
+$m->warning_like(qr/Permission denied/, "got a permission denied warning");
+
+$user_obj->PrincipalObj->GrantRight(Right => 'ModifyOwnDashboard', Object => $RT::System);
+
+# Modify itself is no longer good enough, you need Create
+$m->get_ok($url."Dashboards/Modify.html?Create=1");
+$m->content_contains("Permission denied");
+$m->content_lacks("Save Changes");
+
+$m->warning_like(qr/Permission denied/, "got a permission denied warning");
+
+$user_obj->PrincipalObj->GrantRight(Right => 'CreateOwnDashboard', Object => $RT::System);
+
+$m->get_ok($url."Dashboards/Modify.html?Create=1");
+$m->content_lacks("Permission denied");
+$m->content_contains("Create");
+
+$m->get_ok($url."Dashboards/index.html");
+$m->content_contains("New", "'New' link because we now have ModifyOwnDashboard");
+
+$m->follow_link_ok({text => "New"});
+$m->form_name('ModifyDashboard');
+$m->field("Name" => 'different dashboard');
+$m->content_lacks('Delete', "Delete button hidden because we are creating");
+$m->click_button(value => 'Create');
+$m->content_lacks("No permission to create dashboards");
+$m->content_contains("Saved dashboard different dashboard");
+$m->content_lacks('Delete', "Delete button hidden because we lack DeleteOwnDashboard");
+
+$m->get_ok($url."Dashboards/index.html");
+$m->content_lacks("different dashboard", "we lack SeeOwnDashboard");
+
+$user_obj->PrincipalObj->GrantRight(Right => 'SeeOwnDashboard', Object => $RT::System);
+
+$m->get_ok($url."Dashboards/index.html");
+$m->content_contains("different dashboard", "we now have SeeOwnDashboard");
+$m->content_lacks("Permission denied");
+
+$m->follow_link_ok({text => "different dashboard"});
+$m->content_contains("Basics");
+$m->content_contains("Queries");
+$m->content_lacks("Subscription", "we don't have the SubscribeDashboard right");
+
+$m->follow_link_ok({text => "Basics"});
+$m->content_contains("Modify the dashboard different dashboard");
+
+$m->follow_link_ok({text => "Queries"});
+$m->content_contains("Modify the queries of dashboard different dashboard");
+$m->form_name('Dashboard-Searches-body');
+$m->field('Searches-body-Available' => ["search-2-RT::System-1"]);
+$m->click_button(name => 'add');
+$m->content_contains("Dashboard updated");
+
+my $dashboard = RT::Dashboard->new($currentuser);
+my ($id) = $m->content =~ /name="id" value="(\d+)"/;
+ok($id, "got an ID, $id");
+$dashboard->LoadById($id);
+is($dashboard->Name, "different dashboard");
+
+is($dashboard->Privacy, 'RT::User-' . $user_obj->Id, "correct privacy");
+is($dashboard->PossibleHiddenSearches, 0, "all searches are visible");
+
+my @searches = $dashboard->Searches;
+is(@searches, 1, "one saved search in the dashboard");
+like($searches[0]->Name, qr/newest unowned tickets/, "correct search name");
+
+$m->form_name('Dashboard-Searches-body');
+$m->field('Searches-body-Available' => ["search-1-RT::System-1"]);
+$m->click_button(name => 'add');
+$m->content_contains("Dashboard updated");
+
+RT::Record->FlushCache if RT::Record->can('FlushCache');
+$dashboard = RT::Dashboard->new($currentuser);
+$dashboard->LoadById($id);
+
+@searches = $dashboard->Searches;
+is(@searches, 2, "two saved searches in the dashboard");
+like($searches[0]->Name, qr/newest unowned tickets/, "correct existing search name");
+like($searches[1]->Name, qr/highest priority tickets I own/, "correct new search name");
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+$ticket->Create(
+ Queue => $queue->Id,
+ Requestor => [ $user_obj->Name ],
+ Owner => $user_obj,
+ Subject => 'dashboard test',
+);
+
+$m->follow_link_ok({text => 'different dashboard'});
+$m->content_contains("20 highest priority tickets I own");
+$m->content_contains("20 newest unowned tickets");
+$m->content_lacks("Bookmarked Tickets");
+$m->content_contains("dashboard test", "ticket subject");
+
+$m->get_ok("/Dashboards/$id/This fragment left intentionally blank");
+$m->content_contains("20 highest priority tickets I own");
+$m->content_contains("20 newest unowned tickets");
+$m->content_lacks("Bookmarked Tickets");
+$m->content_contains("dashboard test", "ticket subject");
+
+$m->get_ok("/Dashboards/Subscription.html?DashboardId=$id");
+$m->form_name('SubscribeDashboard');
+$m->click_button(name => 'Save');
+$m->content_contains("Permission denied");
+$m->warning_like(qr/Unable to subscribe to dashboard.*Permission denied/, "got a permission denied warning when trying to subscribe to a dashboard");
+
+RT::Record->FlushCache if RT::Record->can('FlushCache');
+is($user_obj->Attributes->Named('Subscription'), 0, "no subscriptions");
+
+$user_obj->PrincipalObj->GrantRight(Right => 'SubscribeDashboard', Object => $RT::System);
+
+$m->get_ok("/Dashboards/Modify.html?id=$id");
+$m->follow_link_ok({text => "Subscription"});
+$m->content_contains("Subscribe to dashboard different dashboard");
+$m->content_contains("Unowned Tickets");
+$m->content_contains("My Tickets");
+$m->content_lacks("Bookmarked Tickets", "only dashboard queries show up");
+
+$m->form_name('SubscribeDashboard');
+$m->click_button(name => 'Save');
+$m->content_lacks("Permission denied");
+$m->content_contains("Subscribed to dashboard different dashboard");
+
+RT::Record->FlushCache if RT::Record->can('FlushCache');
+TODO: {
+ local $TODO = "some kind of caching is still happening (it works if I remove the check above)";
+ is($user_obj->Attributes->Named('Subscription'), 1, "we have a subscription");
+};
+
+$m->get_ok("/Dashboards/Modify.html?id=$id");
+$m->follow_link_ok({text => "Subscription"});
+$m->content_contains("Modify the subscription to dashboard different dashboard");
+
+$m->get_ok("/Dashboards/Modify.html?id=$id&Delete=1");
+$m->content_contains("Permission denied", "unable to delete dashboard because we lack DeleteOwnDashboard");
+
+$m->warning_like(qr/Couldn't delete dashboard.*Permission denied/, "got a permission denied warning when trying to delete the dashboard");
+
+$user_obj->PrincipalObj->GrantRight(Right => 'DeleteOwnDashboard', Object => $RT::System);
+
+$m->get_ok("/Dashboards/Modify.html?id=$id");
+$m->content_contains('Delete', "Delete button shows because we have DeleteOwnDashboard");
+
+$m->form_name('ModifyDashboard');
+$m->click_button(name => 'Delete');
+$m->content_contains("Deleted dashboard $id");
+
+$m->get("/Dashboards/Modify.html?id=$id");
+$m->content_lacks("different dashboard", "dashboard was deleted");
+$m->content_contains("Failed to load dashboard $id");
+
+$m->warning_like(qr/Failed to load dashboard.*Couldn't find row/, "the dashboard was deleted");
+
+$user_obj->PrincipalObj->GrantRight(Right => "SuperUser", Object => $RT::System);
+
+# now test that we warn about searches others can't see
+# first create a personal saved search...
+$m->get_ok($url."Search/Build.html");
+$m->follow_link_ok({text => 'Advanced'});
+$m->form_with_fields('Query');
+$m->field(Query => "id > 0");
+$m->submit;
+
+$m->form_with_fields('SavedSearchDescription');
+$m->field(SavedSearchDescription => "personal search");
+$m->click_button(name => "SavedSearchSave");
+
+# then the system-wide dashboard
+$m->get_ok($url."Dashboards/Modify.html?Create=1");
+
+$m->form_name('ModifyDashboard');
+$m->field("Name" => 'system dashboard');
+$m->field("Privacy" => 'RT::System-1');
+$m->content_lacks('Delete', "Delete button hidden because we are creating");
+$m->click_button(value => 'Create');
+$m->content_lacks("No permission to create dashboards");
+$m->content_contains("Saved dashboard system dashboard");
+
+$m->follow_link_ok({text => 'Queries'});
+
+$m->form_name('Dashboard-Searches-body');
+$m->field('Searches-body-Available' => ['search-7-RT::User-22']); # XXX: :( :(
+$m->click_button(name => 'add');
+$m->content_contains("Dashboard updated");
+
+$m->content_contains("The following queries may not be visible to all users who can see this dashboard.");
+
+$m->follow_link_ok({text => 'system dashboard'});
+$m->content_contains("personal search", "saved search shows up");
+$m->content_contains("dashboard test", "matched ticket shows up");
+
+# make sure the onlooker can't see the search...
+$onlooker->PrincipalObj->GrantRight(Right => 'SeeDashboard', Object => $RT::System);
+
+my $omech = RT::Test::Web->new;
+ok $omech->login(onlooker => 'onlooker'), "logged in";
+$omech->get_ok("/Dashboards");
+
+$omech->follow_link_ok({text => 'system dashboard'});
+$omech->content_lacks("personal search", "saved search doesn't show up");
+$omech->content_lacks("dashboard test", "matched ticket doesn't show up");
+
+$m->warning_like(qr/User .* tried to load container user /, "can't see other users' personal searches");
+
diff --git a/rt/t/web/gnupg-outgoing.t b/rt/t/web/gnupg-outgoing.t
new file mode 100644
index 000000000..a46833c6c
--- /dev/null
+++ b/rt/t/web/gnupg-outgoing.t
@@ -0,0 +1,363 @@
+#!/usr/bin/perl -w
+use strict;
+use warnings;
+
+use RT::Test tests => 492;
+
+plan skip_all => 'GnuPG required.'
+ unless eval 'use GnuPG::Interface; 1';
+plan skip_all => 'gpg executable is required.'
+ unless RT::Test->find_executable('gpg');
+
+
+use RT::Action::SendEmail;
+use File::Temp qw(tempdir);
+
+RT::Test->set_mail_catcher;
+
+use_ok('RT::Crypt::GnuPG');
+
+RT->Config->Set( GnuPG =>
+ Enable => 1,
+ OutgoingMessagesFormat => 'RFC',
+);
+
+RT->Config->Set( GnuPGOptions =>
+ homedir => scalar tempdir( CLEANUP => 1 ),
+ passphrase => 'rt-test',
+ 'no-permission-warning' => undef,
+ 'trust-model' => 'always',
+);
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::GnuPG' );
+
+RT::Test->import_gnupg_key('rt-recipient@example.com');
+RT::Test->import_gnupg_key('rt-test@example.com', 'public');
+
+my $queue = RT::Test->load_or_create_queue(
+ Name => 'Regression',
+ CorrespondAddress => 'rt-recipient@example.com',
+ CommentAddress => 'rt-recipient@example.com',
+);
+ok $queue && $queue->id, 'loaded or created queue';
+
+RT::Test->set_rights(
+ Principal => 'Everyone',
+ Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
+);
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'logged in';
+
+my @variants = (
+ {},
+ { Sign => 1 },
+ { Encrypt => 1 },
+ { Sign => 1, Encrypt => 1 },
+);
+
+# collect emails
+my %mail = (
+ plain => [],
+ signed => [],
+ encrypted => [],
+ signed_encrypted => [],
+);
+
+diag "check in read-only mode that queue's props influence create/update ticket pages" if $ENV{TEST_VERBOSE};
+{
+ foreach my $variant ( @variants ) {
+ set_queue_crypt_options( %$variant );
+ $m->goto_create_ticket( $queue );
+ $m->form_name('TicketCreate');
+ if ( $variant->{'Encrypt'} ) {
+ ok $m->value('Encrypt', 2), "encrypt tick box is checked";
+ } else {
+ ok !$m->value('Encrypt', 2), "encrypt tick box is unchecked";
+ }
+ if ( $variant->{'Sign'} ) {
+ ok $m->value('Sign', 2), "sign tick box is checked";
+ } else {
+ ok !$m->value('Sign', 2), "sign tick box is unchecked";
+ }
+ }
+
+ # to avoid encryption/signing during create
+ set_queue_crypt_options();
+
+ my $ticket = RT::Ticket->new( $RT::SystemUser );
+ my ($id) = $ticket->Create(
+ Subject => 'test',
+ Queue => $queue->id,
+ Requestor => 'rt-test@example.com',
+ );
+ ok $id, 'ticket created';
+
+ foreach my $variant ( @variants ) {
+ set_queue_crypt_options( %$variant );
+ $m->goto_ticket( $id );
+ $m->follow_link_ok({text => 'Reply'}, '-> reply');
+ $m->form_number(3);
+ if ( $variant->{'Encrypt'} ) {
+ ok $m->value('Encrypt', 2), "encrypt tick box is checked";
+ } else {
+ ok !$m->value('Encrypt', 2), "encrypt tick box is unchecked";
+ }
+ if ( $variant->{'Sign'} ) {
+ ok $m->value('Sign', 2), "sign tick box is checked";
+ } else {
+ ok !$m->value('Sign', 2), "sign tick box is unchecked";
+ }
+ }
+}
+
+# create a ticket for each combination
+foreach my $queue_set ( @variants ) {
+ set_queue_crypt_options( %$queue_set );
+ foreach my $ticket_set ( @variants ) {
+ create_a_ticket( %$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 $queue_set ( @variants ) {
+ set_queue_crypt_options( %$queue_set );
+ foreach my $ticket_set ( @variants ) {
+ update_ticket( $tid, %$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';
+
+foreach my $mail ( map cleanup_headers($_), @{ $mail{'plain'} } ) {
+ 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};
+
+ 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";
+
+ like $msg->Content, qr/Some content/, "RT's mail includes copy of ticket text";
+}
+
+foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed'} } ) {
+ 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};
+
+ 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";
+
+ like $attachments[0]->Content, qr/Some content/,
+ "RT's mail includes copy of ticket text";
+}
+
+foreach my $mail ( map cleanup_headers($_), @{ $mail{'encrypted'} } ) {
+ 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};
+
+ 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";
+
+ like $attachments[0]->Content, qr/Some content/,
+ "RT's mail includes copy of ticket text";
+}
+
+foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed_encrypted'} } ) {
+ 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};
+
+ 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";
+
+ like $attachments[0]->Content, qr/Some content/,
+ "RT's mail includes copy of ticket text";
+}
+
+sub create_a_ticket {
+ 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";
+
+ unlike($m->content, qr/unable to sign outgoing email messages/);
+
+ $m->get_ok('/'); # ensure that the mail has been processed
+
+ my @mail = RT::Test->fetch_caught_mails;
+ check_text_emails( \%args, @mail );
+}
+
+sub update_ticket {
+ my $tid = shift;
+ my %args = (@_);
+
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_ticket( $tid ), "UI -> ticket #$tid";
+ $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' );
+ $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_like(qr/Message recorded/, 'Message recorded') or diag $m->content;
+
+ $m->get_ok('/'); # ensure that the mail has been processed
+
+ my @mail = RT::Test->fetch_caught_mails;
+ check_text_emails( \%args, @mail );
+}
+
+sub check_text_emails {
+ my %args = %{ shift @_ };
+ my @mail = @_;
+
+ ok scalar @mail, "got some mail";
+ for my $mail (@mail) {
+ if ( $args{'Encrypt'} ) {
+ unlike $mail, qr/Some content/, "outgoing email was encrypted";
+ } else {
+ like $mail, qr/Some content/, "outgoing email was not encrypted";
+ }
+ 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';
+ }
+ }
+ 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 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 %args = @_;
+ $m->get_ok("/Admin/Queues/Modify.html?id=". $queue->id);
+ $m->form_with_fields('Sign', 'Encrypt');
+ foreach my $opt ('Sign', 'Encrypt') {
+ if ( $args{$opt} ) {
+ $m->tick($opt => 1);
+ } else {
+ $m->untick($opt => 1);
+ }
+ }
+ $m->submit;
+}
+
diff --git a/rt/t/web/gnupg-select-keys-on-create.t b/rt/t/web/gnupg-select-keys-on-create.t
new file mode 100644
index 000000000..deee6b291
--- /dev/null
+++ b/rt/t/web/gnupg-select-keys-on-create.t
@@ -0,0 +1,325 @@
+#!/usr/bin/perl -w
+use strict;
+use warnings;
+
+use RT::Test tests => 60;
+
+plan skip_all => 'GnuPG required.'
+ unless eval 'use GnuPG::Interface; 1';
+plan skip_all => 'gpg executable is required.'
+ unless RT::Test->find_executable('gpg');
+
+
+use RT::Action::SendEmail;
+use File::Temp qw(tempdir);
+
+RT::Test->set_mail_catcher;
+
+use_ok('RT::Crypt::GnuPG');
+
+RT->Config->Set( GnuPG =>
+ Enable => 1,
+ OutgoingMessagesFormat => 'RFC',
+);
+
+RT->Config->Set( GnuPGOptions =>
+ homedir => scalar tempdir( CLEANUP => 0 ),
+ passphrase => 'rt-test',
+ 'no-permission-warning' => undef,
+);
+diag "GnuPG --homedir ". RT->Config->Get('GnuPGOptions')->{'homedir'} if $ENV{TEST_VERBOSE};
+
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::GnuPG' );
+
+my $queue = RT::Test->load_or_create_queue(
+ Name => 'Regression',
+ CorrespondAddress => 'rt-recipient@example.com',
+ CommentAddress => 'rt-recipient@example.com',
+);
+ok $queue && $queue->id, 'loaded or created queue';
+
+RT::Test->set_rights(
+ Principal => 'Everyone',
+ Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
+);
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'logged in';
+
+diag "check that signing doesn't work if there is no key" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_create_ticket( $queue ), "UI -> create ticket";
+ $m->form_number(3);
+ $m->tick( Sign => 1 );
+ $m->field( Requestors => 'rt-test@example.com' );
+ $m->field( Content => 'Some content' );
+ $m->submit;
+ $m->content_like(
+ qr/unable to sign outgoing email messages/i,
+ 'problems with passphrase'
+ );
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+}
+
+{
+ RT::Test->import_gnupg_key('rt-recipient@example.com');
+ RT::Test->trust_gnupg_key('rt-recipient@example.com');
+ my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-recipient@example.com');
+ is $res{'info'}[0]{'TrustTerse'}, 'ultimate', 'ultimately trusted key';
+}
+
+diag "check that things don't work if there is no key" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_create_ticket( $queue ), "UI -> create ticket";
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( Requestors => 'rt-test@example.com' );
+ $m->field( Content => 'Some content' );
+ $m->submit;
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There is no key suitable for encryption/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok !$form->find_input( 'UseKey-rt-test@example.com' ), 'no key selector';
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+}
+
+diag "import first key of rt-test\@example.com" if $ENV{TEST_VERBOSE};
+my $fpr1 = '';
+{
+ RT::Test->import_gnupg_key('rt-test@example.com', 'public');
+ my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test@example.com');
+ is $res{'info'}[0]{'TrustLevel'}, 0, 'is not trusted key';
+ $fpr1 = $res{'info'}[0]{'Fingerprint'};
+}
+
+diag "check that things still doesn't work if key is not trusted" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_create_ticket( $queue ), "UI -> create ticket";
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( Requestors => 'rt-test@example.com' );
+ $m->field( Content => 'Some content' );
+ $m->submit;
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There is one suitable key, but trust level is not set/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector';
+ is scalar $input->possible_values, 1, 'one option';
+
+ $m->select( 'UseKey-rt-test@example.com' => $fpr1 );
+ $m->submit;
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/Selected key either is not trusted/i,
+ 'problems with keys'
+ );
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+}
+
+diag "import a second key of rt-test\@example.com" if $ENV{TEST_VERBOSE};
+my $fpr2 = '';
+{
+ RT::Test->import_gnupg_key('rt-test@example.com.2', 'public');
+ my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test@example.com');
+ is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
+ $fpr2 = $res{'info'}[2]{'Fingerprint'};
+}
+
+diag "check that things still doesn't work if two keys are not trusted" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_create_ticket( $queue ), "UI -> create ticket";
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( Requestors => 'rt-test@example.com' );
+ $m->field( Content => 'Some content' );
+ $m->submit;
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There are several keys suitable for encryption/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector';
+ is scalar $input->possible_values, 2, 'two options';
+
+ $m->select( 'UseKey-rt-test@example.com' => $fpr1 );
+ $m->submit;
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/Selected key either is not trusted/i,
+ 'problems with keys'
+ );
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+}
+
+{
+ RT::Test->lsign_gnupg_key( $fpr1 );
+ my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test@example.com');
+ ok $res{'info'}[0]{'TrustLevel'} > 0, 'trusted key';
+ is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
+}
+
+diag "check that we see key selector even if only one key is trusted but there are more keys";
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_create_ticket( $queue ), "UI -> create ticket";
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( Requestors => 'rt-test@example.com' );
+ $m->field( Content => 'Some content' );
+ $m->submit;
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There are several keys suitable for encryption/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector';
+ is scalar $input->possible_values, 2, 'two options';
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+}
+
+diag "check that key selector works and we can select trusted key";
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_create_ticket( $queue ), "UI -> create ticket";
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( Requestors => 'rt-test@example.com' );
+ $m->field( Content => 'Some content' );
+ $m->submit;
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There are several keys suitable for encryption/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector';
+ is scalar $input->possible_values, 2, 'two options';
+
+ $m->select( 'UseKey-rt-test@example.com' => $fpr1 );
+ $m->submit;
+ $m->content_like( qr/Ticket \d+ created in queue/i, 'ticket created' );
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok @mail, 'there are some emails';
+ check_text_emails( { Encrypt => 1 }, @mail );
+}
+
+diag "check encrypting of attachments";
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_create_ticket( $queue ), "UI -> create ticket";
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( Requestors => 'rt-test@example.com' );
+ $m->field( Content => 'Some content' );
+ $m->field( Attach => $0 );
+ $m->submit;
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There are several keys suitable for encryption/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector';
+ is scalar $input->possible_values, 2, 'two options';
+
+ $m->select( 'UseKey-rt-test@example.com' => $fpr1 );
+ $m->submit;
+ $m->content_like( qr/Ticket \d+ created in queue/i, 'ticket created' );
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok @mail, 'there are some emails';
+ check_text_emails( { Encrypt => 1, Attachment => 1 }, @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';
+ }
+ }
+ }
+}
+
diff --git a/rt/t/web/gnupg-select-keys-on-update.t b/rt/t/web/gnupg-select-keys-on-update.t
new file mode 100644
index 000000000..76817ddf2
--- /dev/null
+++ b/rt/t/web/gnupg-select-keys-on-update.t
@@ -0,0 +1,344 @@
+#!/usr/bin/perl -w
+use strict;
+use warnings;
+
+use RT::Test tests => 68;
+
+plan skip_all => 'GnuPG required.'
+ unless eval 'use GnuPG::Interface; 1';
+plan skip_all => 'gpg executable is required.'
+ unless RT::Test->find_executable('gpg');
+
+
+use RT::Action::SendEmail;
+use File::Temp qw(tempdir);
+
+RT::Test->set_mail_catcher;
+
+use_ok('RT::Crypt::GnuPG');
+
+RT->Config->Set( GnuPG =>
+ Enable => 1,
+ OutgoingMessagesFormat => 'RFC',
+);
+
+RT->Config->Set( GnuPGOptions =>
+ homedir => scalar tempdir( CLEANUP => 0 ),
+ passphrase => 'rt-test',
+ 'no-permission-warning' => undef,
+);
+diag "GnuPG --homedir ". RT->Config->Get('GnuPGOptions')->{'homedir'} if $ENV{TEST_VERBOSE};
+
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::GnuPG' );
+
+my $queue = RT::Test->load_or_create_queue(
+ Name => 'Regression',
+ CorrespondAddress => 'rt-recipient@example.com',
+ CommentAddress => 'rt-recipient@example.com',
+);
+ok $queue && $queue->id, 'loaded or created queue';
+
+RT::Test->set_rights(
+ Principal => 'Everyone',
+ Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
+);
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'logged in';
+
+
+my $tid;
+{
+ my $ticket = RT::Ticket->new( $RT::SystemUser );
+ ($tid) = $ticket->Create(
+ Subject => 'test',
+ Queue => $queue->id,
+ );
+ ok $tid, 'ticket created';
+}
+
+diag "check that signing doesn't work if there is no key" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_ticket( $tid ), "UI -> ticket #$tid";
+ $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' );
+ $m->form_number(3);
+ $m->tick( Sign => 1 );
+ $m->field( UpdateCc => 'rt-test@example.com' );
+ $m->field( UpdateContent => 'Some content' );
+ $m->click('SubmitTicket');
+ $m->content_like(
+ qr/unable to sign outgoing email messages/i,
+ 'problems with passphrase'
+ );
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+}
+
+{
+ RT::Test->import_gnupg_key('rt-recipient@example.com');
+ RT::Test->trust_gnupg_key('rt-recipient@example.com');
+ my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-recipient@example.com');
+ is $res{'info'}[0]{'TrustTerse'}, 'ultimate', 'ultimately trusted key';
+}
+
+diag "check that things don't work if there is no key" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_ticket( $tid ), "UI -> ticket #$tid";
+ $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' );
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( UpdateCc => 'rt-test@example.com' );
+ $m->field( UpdateContent => 'Some content' );
+ $m->click('SubmitTicket');
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There is no key suitable for encryption/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok !$form->find_input( 'UseKey-rt-test@example.com' ), 'no key selector';
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+}
+
+
+diag "import first key of rt-test\@example.com" if $ENV{TEST_VERBOSE};
+my $fpr1 = '';
+{
+ RT::Test->import_gnupg_key('rt-test@example.com', 'public');
+ my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test@example.com');
+ is $res{'info'}[0]{'TrustLevel'}, 0, 'is not trusted key';
+ $fpr1 = $res{'info'}[0]{'Fingerprint'};
+}
+
+diag "check that things still doesn't work if key is not trusted" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_ticket( $tid ), "UI -> ticket #$tid";
+ $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' );
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( UpdateCc => 'rt-test@example.com' );
+ $m->field( UpdateContent => 'Some content' );
+ $m->click('SubmitTicket');
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There is one suitable key, but trust level is not set/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector';
+ is scalar $input->possible_values, 1, 'one option';
+
+ $m->select( 'UseKey-rt-test@example.com' => $fpr1 );
+ $m->click('SubmitTicket');
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/Selected key either is not trusted/i,
+ 'problems with keys'
+ );
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+}
+
+diag "import a second key of rt-test\@example.com" if $ENV{TEST_VERBOSE};
+my $fpr2 = '';
+{
+ RT::Test->import_gnupg_key('rt-test@example.com.2', 'public');
+ my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test@example.com');
+ is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
+ $fpr2 = $res{'info'}[2]{'Fingerprint'};
+}
+
+diag "check that things still doesn't work if two keys are not trusted" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_ticket( $tid ), "UI -> ticket #$tid";
+ $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' );
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( UpdateCc => 'rt-test@example.com' );
+ $m->field( UpdateContent => 'Some content' );
+ $m->click('SubmitTicket');
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There are several keys suitable for encryption/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector';
+ is scalar $input->possible_values, 2, 'two options';
+
+ $m->select( 'UseKey-rt-test@example.com' => $fpr1 );
+ $m->click('SubmitTicket');
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/Selected key either is not trusted/i,
+ 'problems with keys'
+ );
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+}
+
+{
+ RT::Test->lsign_gnupg_key( $fpr1 );
+ my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test@example.com');
+ ok $res{'info'}[0]{'TrustLevel'} > 0, 'trusted key';
+ is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
+}
+
+diag "check that we see key selector even if only one key is trusted but there are more keys" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_ticket( $tid ), "UI -> ticket #$tid";
+ $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' );
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( UpdateCc => 'rt-test@example.com' );
+ $m->field( UpdateContent => 'Some content' );
+ $m->click('SubmitTicket');
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There are several keys suitable for encryption/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector';
+ is scalar $input->possible_values, 2, 'two options';
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok !@mail, 'there are no outgoing emails';
+}
+
+diag "check that key selector works and we can select trusted key" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_ticket( $tid ), "UI -> ticket #$tid";
+ $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' );
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( UpdateCc => 'rt-test@example.com' );
+ $m->field( UpdateContent => 'Some content' );
+ $m->click('SubmitTicket');
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There are several keys suitable for encryption/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector';
+ is scalar $input->possible_values, 2, 'two options';
+
+ $m->select( 'UseKey-rt-test@example.com' => $fpr1 );
+ $m->click('SubmitTicket');
+ $m->content_like( qr/Message recorded/i, 'Message recorded' );
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok @mail, 'there are some emails';
+ check_text_emails( { Encrypt => 1 }, @mail );
+}
+
+diag "check encrypting of attachments" if $ENV{TEST_VERBOSE};
+{
+ RT::Test->clean_caught_mails;
+
+ ok $m->goto_ticket( $tid ), "UI -> ticket #$tid";
+ $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' );
+ $m->form_number(3);
+ $m->tick( Encrypt => 1 );
+ $m->field( UpdateCc => 'rt-test@example.com' );
+ $m->field( UpdateContent => 'Some content' );
+ $m->field( Attach => $0 );
+ $m->click('SubmitTicket');
+ $m->content_like(
+ qr/You are going to encrypt outgoing email messages/i,
+ 'problems with keys'
+ );
+ $m->content_like(
+ qr/There are several keys suitable for encryption/i,
+ 'problems with keys'
+ );
+
+ my $form = $m->form_number(3);
+ ok my $input = $form->find_input( 'UseKey-rt-test@example.com' ), 'found key selector';
+ is scalar $input->possible_values, 2, 'two options';
+
+ $m->select( 'UseKey-rt-test@example.com' => $fpr1 );
+ $m->click('SubmitTicket');
+ $m->content_like( qr/Message recorded/i, 'Message recorded' );
+
+ my @mail = RT::Test->fetch_caught_mails;
+ ok @mail, 'there are some emails';
+ check_text_emails( { Encrypt => 1, Attachment => 1 }, @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';
+ }
+ }
+ }
+}
+
diff --git a/rt/t/web/offline_messages_utf8.t b/rt/t/web/offline_messages_utf8.t
new file mode 100644
index 000000000..c32e0bc27
--- /dev/null
+++ b/rt/t/web/offline_messages_utf8.t
@@ -0,0 +1,67 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 6;
+use File::Temp qw/tempfile/;
+use Encode;
+use RT::Ticket;
+
+my ( $url, $m ) = RT::Test->started_ok;
+$m->default_header( 'Accept-Language' => "zh-cn" );
+ok( $m->login, 'logged in' );
+
+my $ticket_id;
+my $template;
+
+{
+
+ # test create message
+ $template = <<EOF;
+===Create-Ticket: ticket1
+Queue: General
+Subject: test message
+Status: new
+Content:
+ENDOFCONTENT
+Due:
+TimeEstimated: 100
+TimeLeft: 100
+FinalPriority: 90
+EOF
+
+ $m->get_ok( $url . '/Tools/Offline.html' );
+
+ $m->submit_form(
+ form_name => 'TicketUpdate',
+ fields => { string => $template, },
+ button => 'UpdateTickets',
+ );
+ my $content = encode 'utf8', $m->content;
+ ok( $content =~ qr/申请单 #(\d+) 成功新增于 &#39;General&#39; 表单/, 'message is shown right' );
+ $ticket_id = $1;
+}
+
+{
+
+ # test update message
+ $template = <<EOF;
+===Update-Ticket: 1
+Subject: test message update
+EOF
+
+ $m->get_ok( $url . '/Tools/Offline.html' );
+ $m->submit_form(
+ form_name => 'TicketUpdate',
+ fields => { string => $template, },
+ button => 'UpdateTickets',
+ );
+
+ my $content = encode 'utf8', $m->content;
+ ok(
+ $content =~
+qr/主题\s*的值从\s*&#39;test message&#39;\s*改为\s*&#39;test message update&#39;/,
+ 'subject is updated'
+ );
+}
+
diff --git a/rt/t/web/offline_utf8.t b/rt/t/web/offline_utf8.t
new file mode 100644
index 000000000..2a3e64d3c
--- /dev/null
+++ b/rt/t/web/offline_utf8.t
@@ -0,0 +1,54 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 8;
+use File::Temp qw/tempfile/;
+use Encode;
+use RT::Ticket;
+my ( $fh, $file ) = tempfile;
+my $template = <<EOF;
+===Create-Ticket: ticket1
+Queue: General
+Subject: 标题
+Status: new
+Content:
+这是正文
+ENDOFCONTENT
+EOF
+
+print $fh $template;
+close $fh;
+
+my ( $url, $m ) = RT::Test->started_ok;
+ok( $m->login, 'logged in' );
+
+$m->get_ok( $url . '/Tools/Offline.html' );
+
+$m->submit_form(
+ form_name => 'TicketUpdate',
+ fields => { Template => $file, },
+ button => 'Parse',
+);
+
+$m->content_contains( '这是正文', 'content is parsed right' );
+
+$m->submit_form(
+ form_name => 'TicketUpdate',
+ button => 'UpdateTickets',
+
+ # mimic what browsers do: they seems decoded $template
+ fields => { string => decode( 'utf8', $template ), },
+);
+
+$m->content_like( qr/Ticket \d+ created/, 'found ticket created message' );
+my ( $ticket_id ) = $m->content =~ /Ticket (\d+) created/;
+
+my $ticket = RT::Ticket->new( $RT::SystemUser );
+$ticket->Load( $ticket_id );
+is( $ticket->Subject, '标题', 'subject in $ticket is right' );
+
+$m->get_ok( $url . "/Ticket/Display.html?id=$ticket_id" );
+$m->content_contains( '这是正文',
+ 'content is right in ticket display page' );
+
diff --git a/rt/t/web/query_builder.t b/rt/t/web/query_builder.t
new file mode 100644
index 000000000..02ed1297f
--- /dev/null
+++ b/rt/t/web/query_builder.t
@@ -0,0 +1,249 @@
+#!/usr/bin/perl
+
+use strict;
+use HTTP::Request::Common;
+use HTTP::Cookies;
+use LWP;
+use Encode;
+use RT::Test tests => 42;
+
+my $cookie_jar = HTTP::Cookies->new;
+my ($baseurl, $agent) = RT::Test->started_ok;
+
+
+# give the agent a place to stash the cookies
+
+$agent->cookie_jar($cookie_jar);
+
+# create a regression queue if it doesn't exist
+my $queue = RT::Test->load_or_create_queue( Name => 'Regression' );
+ok $queue && $queue->id, 'loaded or created queue';
+
+my $url = $agent->rt_base_url;
+ok $agent->login, "logged in";
+
+# {{{ Query Builder tests
+
+my $response = $agent->get($url."Search/Build.html");
+ok $response->is_success, "Fetched ". $url ."Search/Build.html";
+
+sub getQueryFromForm {
+ $agent->form_name('BuildQuery');
+ # This pulls out the "hidden input" query from the page
+ my $q = $agent->current_form->find_input("Query")->value;
+ $q =~ s/^\s+//g;
+ $q =~ s/\s+$//g;
+ $q =~ s/\s+/ /g;
+ return $q;
+}
+
+sub selectedClauses {
+ my @clauses = grep { defined } map { $_->value } $agent->current_form->find_input("clauses");
+ return [ @clauses ];
+}
+
+
+diag "add the first condition" if $ENV{'TEST_VERBOSE'};
+{
+ ok $agent->form_name('BuildQuery'), "found the form once";
+ $agent->field("ActorField", "Owner");
+ $agent->field("ActorOp", "=");
+ $agent->field("ValueOfActor", "Nobody");
+ $agent->submit;
+ is getQueryFromForm, "Owner = 'Nobody'", 'correct query';
+}
+
+diag "set the next condition" if $ENV{'TEST_VERBOSE'};
+{
+ ok($agent->form_name('BuildQuery'), "found the form again");
+ $agent->field("QueueOp", "!=");
+ $agent->field("ValueOfQueue", "Regression");
+ $agent->submit;
+ is getQueryFromForm, "Owner = 'Nobody' AND Queue != 'Regression'",
+ 'correct query';
+}
+
+diag "We're going to delete the owner" if $ENV{'TEST_VERBOSE'};
+{
+ $agent->select("clauses", ["0"] );
+ $agent->click("DeleteClause");
+ ok $agent->form_name('BuildQuery'), "found the form";
+ is getQueryFromForm, "Queue != 'Regression'", 'correct query';
+}
+
+diag "add a cond with OR and se number by the way" if $ENV{'TEST_VERBOSE'};
+{
+ $agent->field("AndOr", "OR");
+ $agent->select("idOp", ">");
+ $agent->field("ValueOfid" => "1234");
+ $agent->click("AddClause");
+ ok $agent->form_name('BuildQuery'), "found the form again";
+ is getQueryFromForm, "Queue != 'Regression' OR id > 1234",
+ "added something as OR, and number not quoted";
+ is_deeply selectedClauses, ["1"], 'the id that we just entered is still selected';
+
+}
+
+diag "Move the second one up a level" if $ENV{'TEST_VERBOSE'};
+{
+ $agent->click("Up");
+ ok $agent->form_name('BuildQuery'), "found the form again";
+ is getQueryFromForm, "id > 1234 OR Queue != 'Regression'", "moved up one";
+ is_deeply selectedClauses, ["0"], 'the one we moved up is selected';
+}
+
+diag "Move the second one right" if $ENV{'TEST_VERBOSE'};
+{
+ $agent->click("Right");
+ ok $agent->form_name('BuildQuery'), "found the form again";
+ is getQueryFromForm, "Queue != 'Regression' OR ( id > 1234 )",
+ "moved over to the right (and down)";
+ is_deeply selectedClauses, ["2"], 'the one we moved right is selected';
+}
+
+diag "Move the block up" if $ENV{'TEST_VERBOSE'};
+{
+ $agent->select("clauses", ["1"]);
+ $agent->click("Up");
+ ok $agent->form_name('BuildQuery'), "found the form again";
+ is getQueryFromForm, "( id > 1234 ) OR Queue != 'Regression'", "moved up";
+ is_deeply selectedClauses, ["0"], 'the one we moved up is selected';
+}
+
+
+diag "Can not move up the top most clause" if $ENV{'TEST_VERBOSE'};
+{
+ $agent->select("clauses", ["0"]);
+ $agent->click("Up");
+ ok $agent->form_name('BuildQuery'), "found the form again";
+ $agent->content_like(qr/error: can\S+t move up/, "i shouldn't have been able to hit up");
+ is_deeply selectedClauses, ["0"], 'the one we tried to move is selected';
+}
+
+diag "Can not move left the left most clause" if $ENV{'TEST_VERBOSE'};
+{
+ $agent->click("Left");
+ ok($agent->form_name('BuildQuery'), "found the form again");
+ $agent->content_like(qr/error: can\S+t move left/, "i shouldn't have been able to hit left");
+ is_deeply selectedClauses, ["0"], 'the one we tried to move is selected';
+}
+
+diag "Add a condition into a nested block" if $ENV{'TEST_VERBOSE'};
+{
+ $agent->select("clauses", ["1"]);
+ $agent->select("ValueOfStatus" => "stalled");
+ $agent->submit;
+ ok $agent->form_name('BuildQuery'), "found the form again";
+ is_deeply selectedClauses, ["2"], 'the one we added is only selected';
+ is getQueryFromForm,
+ "( id > 1234 AND Status = 'stalled' ) OR Queue != 'Regression'",
+ "added new one";
+}
+
+diag "click advanced, enter 'C1 OR ( C2 AND C3 )', apply, aggregators should stay the same."
+ if $ENV{'TEST_VERBOSE'};
+{
+ my $response = $agent->get($url."Search/Edit.html");
+ ok( $response->is_success, "Fetched /Search/Edit.html" );
+ ok($agent->form_number(3), "found the form");
+ $agent->field("Query", "Status = 'new' OR ( Status = 'open' AND Subject LIKE 'office' )");
+ $agent->submit;
+ is( getQueryFromForm,
+ "Status = 'new' OR ( Status = 'open' AND Subject LIKE 'office' )",
+ "no aggregators change"
+ );
+}
+
+# - new items go one level down
+# - add items at currently selected level
+# - if nothing is selected, add at end, one level down
+#
+# move left
+# - error if nothing selected
+# - same item should be selected after move
+# - can't move left if you're at the top level
+#
+# move right
+# - error if nothing selected
+# - same item should be selected after move
+# - can always move right (no max depth...should there be?)
+#
+# move up
+# - error if nothing selected
+# - same item should be selected after move
+# - can't move up if you're first in the list
+#
+# move down
+# - error if nothing selected
+# - same item should be selected after move
+# - can't move down if you're last in the list
+#
+# toggle
+# - error if nothing selected
+# - change all aggregators in the grouping
+# - don't change any others
+#
+# delete
+# - error if nothing selected
+# - delete currently selected item
+# - delete all children of a grouping
+# - if delete leaves a node with no children, delete that, too
+# - what should be selected?
+#
+# Clear
+# - clears entire query
+# - clears it from the session, too
+
+# }}}
+
+# create a custom field with nonascii name and try to add a condition
+{
+ my $cf = RT::CustomField->new( $RT::SystemUser );
+ $cf->LoadByName( Name => "\x{442}", Queue => 0 );
+ if ( $cf->id ) {
+ is($cf->Type, 'Freeform', 'loaded and type is correct');
+ } else {
+ my ($return, $msg) = $cf->Create(
+ Name => "\x{442}",
+ Queue => 0,
+ Type => 'Freeform',
+ );
+ ok($return, 'created CF') or diag "error: $msg";
+ }
+
+ my $response = $agent->get($url."Search/Build.html?NewQuery=1");
+ ok( $response->is_success, "Fetched " . $url."Search/Build.html" );
+
+ ok($agent->form_name('BuildQuery'), "found the form once");
+ $agent->field("ValueOf'CF.{\x{442}}'", "\x{441}");
+ $agent->submit();
+ is( getQueryFromForm,
+ "'CF.{\x{442}}' LIKE '\x{441}'",
+ "no changes, no duplicate condition with badly encoded text"
+ );
+
+}
+
+diag "input a condition, select (several conditions), click delete"
+ if $ENV{'TEST_VERBOSE'};
+{
+ my $response = $agent->get( $url."Search/Edit.html" );
+ ok $response->is_success, "Fetched /Search/Edit.html";
+ ok $agent->form_number(3), "found the form";
+ $agent->field("Query", "( Status = 'new' OR Status = 'open' )");
+ $agent->submit;
+ is( getQueryFromForm,
+ "( Status = 'new' OR Status = 'open' )",
+ "query is the same"
+ );
+ $agent->select("clauses", [qw(0 1 2)]);
+ $agent->field( ValueOfid => 10 );
+ $agent->click("DeleteClause");
+
+ is( getQueryFromForm,
+ "id < 10",
+ "replaced query successfuly"
+ );
+}
+
+1;
diff --git a/rt/t/web/quicksearch.t b/rt/t/web/quicksearch.t
new file mode 100644
index 000000000..cd9a8e76c
--- /dev/null
+++ b/rt/t/web/quicksearch.t
@@ -0,0 +1,51 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 7;
+my ($baseurl, $m) = RT::Test->started_ok;
+my $url = $m->rt_base_url;
+
+# merged tickets still show up in search
+my $t1 = RT::Ticket->new($RT::SystemUser);
+$t1->Create(
+ Subject => 'base ticket'.$$,
+ Queue => 'general',
+ Owner => 'root',
+ Requestor => 'customsearch@localhost',
+ MIMEObj => MIME::Entity->build(
+ From => 'customsearch@localhost',
+ To => 'rt@localhost',
+ Subject => 'base ticket'.$$,
+ Data => "DON'T SEARCH FOR ME",
+ ),
+);
+ok(my $id1 = $t1->id, 'created ticket for custom search');
+
+my $t2 = RT::Ticket->new($RT::SystemUser);
+$t2->Create(
+ Subject => 'merged away'.$$,
+ Queue => 'general',
+ Owner => 'root',
+ Requestor => 'customsearch@localhost',
+ MIMEObj => MIME::Entity->build(
+ From => 'customsearch@localhost',
+ To => 'rt@localhost',
+ Subject => 'merged away'.$$,
+ Data => "MERGEDAWAY",
+ ),
+);
+ok(my $id2 = $t2->id, 'created ticket for custom search');
+
+my ($ok, $msg) = $t2->MergeInto($id1);
+ok($ok, "merge: $msg");
+
+ok($m->login, 'logged in');
+
+$m->form_with_fields('q');
+$m->field(q => 'fulltext:MERGEDAWAY');
+TODO: {
+ local $TODO = "We don't yet handle merged ticket content searches right";
+$m->content_contains('Found 1 ticket');
+}
+$m->content_contains('base ticket', "base ticket is found, not the merged-away ticket");
diff --git a/rt/t/web/rest-non-ascii-subject.t b/rt/t/web/rest-non-ascii-subject.t
new file mode 100644
index 000000000..70c910afe
--- /dev/null
+++ b/rt/t/web/rest-non-ascii-subject.t
@@ -0,0 +1,55 @@
+#!/usr/bin/env perl
+# Test ticket creation with REST using non ascii subject
+use strict;
+use warnings;
+use RT::Test tests => 7;
+
+local $RT::Test::SKIP_REQUEST_WORK_AROUND = 1;
+
+use Encode;
+# \x{XX} where XX is less than 255 is not treated as unicode code point
+my $subject = Encode::decode('latin1', "Sujet accentu\x{e9}");
+my $text = Encode::decode('latin1', "Contenu accentu\x{e9}");
+
+my ($baseurl, $m) = RT::Test->started_ok;
+
+my $queue = RT::Test->load_or_create_queue(Name => 'General');
+ok($queue->Id, "loaded the General queue");
+
+my $content = "id: ticket/new
+Queue: General
+Requestor: root
+Subject: $subject
+Cc:
+AdminCc:
+Owner:
+Status: new
+Priority:
+InitialPriority:
+FinalPriority:
+TimeEstimated:
+Starts: 2009-03-10 16:14:55
+Due: 2009-03-10 16:14:55
+Text: $text";
+
+$m->post("$baseurl/REST/1.0/ticket/new", [
+ user => 'root',
+ pass => 'password',
+# error message from HTTP::Message: content must be bytes
+ content => Encode::encode_utf8($content),
+], Content_Type => 'form-data' );
+
+my ($id) = $m->content =~ /Ticket (\d+) created/;
+ok($id, "got ticket #$id");
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+$ticket->Load($id);
+is($ticket->Id, $id, "loaded the REST-created ticket");
+is($ticket->Subject, $subject, "ticket subject successfully set");
+
+my $attach = $ticket->Transactions->First->Attachments->First;
+is($attach->Subject, $subject, "attachement subject successfully set");
+TODO: {
+ local $TODO = "Not fixed yet, but not a regression";
+ is($attach->GetHeader('Subject'), $subject, "attachement header subject successfully set");
+}
diff --git a/rt/t/web/rest.t b/rt/t/web/rest.t
new file mode 100644
index 000000000..b3a7c558b
--- /dev/null
+++ b/rt/t/web/rest.t
@@ -0,0 +1,71 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use RT::Test tests => 16;
+
+my ($baseurl, $m) = RT::Test->started_ok;
+
+for my $name ("severity", "fu()n:k/") {
+ my $cf = RT::Test->load_or_create_custom_field(
+ Name => $name,
+ Type => 'Freeform',
+ Queue => 'General',
+ );
+ ok($cf->Id, "created a CustomField");
+ is($cf->Name, $name, "correct CF name");
+}
+
+my $queue = RT::Test->load_or_create_queue(Name => 'General');
+ok($queue->Id, "loaded the General queue");
+
+$m->post("$baseurl/REST/1.0/ticket/new", [
+ user => 'root',
+ pass => 'password',
+ format => 'l',
+]);
+
+my $text = $m->content;
+my @lines = $text =~ m{.*}g;
+shift @lines; # header
+
+# CFs aren't in the default ticket form
+push @lines, "CF-fu()n:k/: maximum"; # old style
+push @lines, "CF.{severity}: explosive"; # new style
+
+$text = join "\n", @lines;
+
+ok($text =~ s/Subject:\s*$/Subject: REST interface/m, "successfully replaced subject");
+
+$m->post("$baseurl/REST/1.0/ticket/edit", [
+ user => 'root',
+ pass => 'password',
+
+ content => $text,
+], Content_Type => 'form-data');
+
+my ($id) = $m->content =~ /Ticket (\d+) created/;
+ok($id, "got ticket #$id");
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+$ticket->Load($id);
+is($ticket->Id, $id, "loaded the REST-created ticket");
+is($ticket->Subject, "REST interface", "subject successfully set");
+is($ticket->FirstCustomFieldValue("fu()n:k/"), "maximum", "CF successfully set");
+
+$m->post("$baseurl/REST/1.0/search/ticket", [
+ user => 'root',
+ pass => 'password',
+ query => "id=$id",
+ fields => "Subject,CF-fu()n:k/,CF.{severity},Status",
+]);
+
+# the fields are interpreted server-side a hash (why?), so we can't depend
+# on order
+for ("id: ticket/1",
+ "Subject: REST interface",
+ "CF.{fu()n:k/}: maximum",
+ "CF.{severity}: explosive",
+ "Status: new") {
+ $m->content_contains($_);
+}
+
diff --git a/rt/t/web/rights.t b/rt/t/web/rights.t
new file mode 100644
index 000000000..b47ba99af
--- /dev/null
+++ b/rt/t/web/rights.t
@@ -0,0 +1,85 @@
+#!/usr/bin/perl -w
+use strict;
+use warnings;
+
+use RT::Test tests => 14;
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, "logged in";
+
+$m->follow_link_ok({ text => 'Configuration' });
+$m->follow_link_ok({ text => 'Global' });
+$m->follow_link_ok({ text => 'Group Rights' });
+
+
+sub get_rights {
+ my $agent = shift;
+ my $principal_id = shift;
+ my $object = shift;
+ $agent->form_number(3);
+ my @inputs = $agent->current_form->find_input("RevokeRight-$principal_id-$object");
+ my @rights = sort grep $_, map $_->possible_values, grep $_, @inputs;
+ return @rights;
+};
+
+diag "load Everyone group" if $ENV{'TEST_VERBOSE'};
+my ($everyone, $everyone_gid);
+{
+ $everyone = RT::Group->new( $RT::SystemUser );
+ $everyone->LoadSystemInternalGroup('Everyone');
+ ok($everyone_gid = $everyone->id, "loaded 'everyone' group");
+}
+
+diag "revoke all global rights from Everyone group" if $ENV{'TEST_VERBOSE'};
+my @has = get_rights( $m, $everyone_gid, 'RT::System-1' );
+if ( @has ) {
+ $m->form_number(3);
+ $m->tick("RevokeRight-$everyone_gid-RT::System-1", $_) foreach @has;
+ $m->submit;
+
+ is_deeply([get_rights( $m, $everyone_gid, 'RT::System-1' )], [], 'deleted all rights' );
+} else {
+ ok(1, 'the group has no global rights');
+}
+
+diag "grant SuperUser right to everyone" if $ENV{'TEST_VERBOSE'};
+{
+ $m->form_number(3);
+ $m->select("GrantRight-$everyone_gid-RT::System-1", ['SuperUser']);
+ $m->submit;
+
+ $m->content_contains('Right Granted', 'got message');
+ RT::Principal::InvalidateACLCache();
+ ok($everyone->PrincipalObj->HasRight( Right => 'SuperUser', Object => $RT::System ), 'group has right');
+ is_deeply( [get_rights( $m, $everyone_gid, 'RT::System-1' )], ['SuperUser'], 'granted SuperUser right' );
+}
+
+diag "revoke the right" if $ENV{'TEST_VERBOSE'};
+{
+ $m->form_number(3);
+ $m->tick("RevokeRight-$everyone_gid-RT::System-1", 'SuperUser');
+ $m->submit;
+
+ $m->content_contains('Right revoked', 'got message');
+ RT::Principal::InvalidateACLCache();
+ ok(!$everyone->PrincipalObj->HasRight( Right => 'SuperUser', Object => $RT::System ), 'group has no right');
+ is_deeply( [get_rights( $m, $everyone_gid, 'RT::System-1' )], [], 'revoked SuperUser right' );
+}
+
+
+diag "return rights the group had in the beginning" if $ENV{'TEST_VERBOSE'};
+if ( @has ) {
+ $m->form_number(3);
+ $m->select("GrantRight-$everyone_gid-RT::System-1", \@has);
+ $m->submit;
+
+ $m->content_contains('Right Granted', 'got message');
+ is_deeply(
+ [ get_rights( $m, $everyone_gid, 'RT::System-1' ) ],
+ [ @has ],
+ 'returned back all rights'
+ );
+} else {
+ ok(1, 'the group had no global rights, so nothing to return');
+}
+
diff --git a/rt/t/web/rights1.t b/rt/t/web/rights1.t
new file mode 100644
index 000000000..6da204cc9
--- /dev/null
+++ b/rt/t/web/rights1.t
@@ -0,0 +1,134 @@
+#!/usr/bin/perl -w
+use strict;
+use HTTP::Cookies;
+
+use RT::Test tests => 35;
+my ($baseurl, $agent) = RT::Test->started_ok;
+
+# Create a user with basically no rights, to start.
+my $user_obj = RT::User->new($RT::SystemUser);
+my ($ret, $msg) = $user_obj->LoadOrCreateByEmail('customer-'.$$.'@example.com');
+ok($ret, 'ACL test user creation');
+$user_obj->SetName('customer-'.$$);
+$user_obj->SetPrivileged(1);
+($ret, $msg) = $user_obj->SetPassword('customer');
+ok($ret, "ACL test password set. $msg");
+
+# Now test the web interface, making sure objects come and go as
+# required.
+
+
+my $cookie_jar = HTTP::Cookies->new;
+
+# give the agent a place to stash the cookies
+
+$agent->cookie_jar($cookie_jar);
+
+no warnings 'once';
+# get the top page
+login($agent, $user_obj);
+
+# Test for absence of Configure and Preferences tabs.
+ok(!$agent->find_link( url => "$RT::WebPath/Admin/",
+ text => 'Configuration'), "No config tab" );
+ok(!$agent->find_link( url => "$RT::WebPath/User/Prefs.html",
+ text => 'Preferences'), "No prefs pane" );
+
+# Now test for their presence, one at a time. Sleep for a bit after
+# ACL changes, thanks to the 10s ACL cache.
+my ($grantid,$grantmsg) =$user_obj->PrincipalObj->GrantRight(Right => 'ShowConfigTab', Object => $RT::System);
+
+ok($grantid,$grantmsg);
+
+$agent->reload;
+
+like($agent->{'content'} , qr/Logout/i, "Reloaded page successfully");
+ok($agent->find_link( url => "$RT::WebPath/Admin/",
+ text => 'Configuration'), "Found config tab" );
+my ($revokeid,$revokemsg) =$user_obj->PrincipalObj->RevokeRight(Right => 'ShowConfigTab');
+ok ($revokeid,$revokemsg);
+($grantid,$grantmsg) =$user_obj->PrincipalObj->GrantRight(Right => 'ModifySelf');
+ok ($grantid,$grantmsg);
+$agent->reload();
+like($agent->{'content'} , qr/Logout/i, "Reloaded page successfully");
+ok($agent->find_link(
+ text => 'Preferences'), "Found prefs pane" );
+($revokeid,$revokemsg) = $user_obj->PrincipalObj->RevokeRight(Right => 'ModifySelf');
+ok ($revokeid,$revokemsg);
+# Good. Now load the search page and test Load/Save Search.
+$agent->follow_link( url => "$RT::WebPath/Search/Build.html",
+ text => 'Tickets');
+is($agent->{'status'}, 200, "Fetched search builder page");
+ok($agent->{'content'} !~ /Load saved search/i, "No search loading box");
+ok($agent->{'content'} !~ /Saved searches/i, "No saved searches box");
+
+($grantid,$grantmsg) = $user_obj->PrincipalObj->GrantRight(Right => 'LoadSavedSearch');
+ok($grantid,$grantmsg);
+$agent->reload();
+like($agent->{'content'} , qr/Load saved search/i, "Search loading box exists");
+ok($agent->{'content'} !~ /input\s+type=['"]submit['"][^>]+name=['"]SavedSearchSave['"]/i,
+ "Still no saved searches box");
+
+($grantid,$grantmsg) =$user_obj->PrincipalObj->GrantRight(Right => 'CreateSavedSearch');
+ok ($grantid,$grantmsg);
+$agent->reload();
+like($agent->{'content'} , qr/Load saved search/i,
+ "Search loading box still exists");
+like($agent->{'content'} , qr/input\s+type=['"]submit['"][^>]+name=['"]SavedSearchSave['"]/i,
+ "Saved searches box exists");
+
+# Create a group, and a queue, so we can test limited user visibility
+# via SelectOwner.
+
+my $queue_obj = RT::Queue->new($RT::SystemUser);
+($ret, $msg) = $queue_obj->Create(Name => 'CustomerQueue-'.$$,
+ Description => 'queue for SelectOwner testing');
+ok($ret, "SelectOwner test queue creation. $msg");
+my $group_obj = RT::Group->new($RT::SystemUser);
+($ret, $msg) = $group_obj->CreateUserDefinedGroup(Name => 'CustomerGroup-'.$$,
+ Description => 'group for SelectOwner testing');
+ok($ret, "SelectOwner test group creation. $msg");
+
+# Add our customer to the customer group, and give it queue rights.
+($ret, $msg) = $group_obj->AddMember($user_obj->PrincipalObj->Id());
+ok($ret, "Added customer to its group. $msg");
+($grantid,$grantmsg) =$group_obj->PrincipalObj->GrantRight(Right => 'OwnTicket',
+ Object => $queue_obj);
+
+ok($grantid,$grantmsg);
+($grantid,$grantmsg) =$group_obj->PrincipalObj->GrantRight(Right => 'SeeQueue',
+ Object => $queue_obj);
+ok ($grantid,$grantmsg);
+# Now. When we look at the search page we should be able to see
+# ourself in the list of possible owners.
+
+$agent->reload();
+ok($agent->form_name('BuildQuery'), "Yep, form is still there");
+my $input = $agent->current_form->find_input('ValueOfActor');
+ok(grep(/customer-$$/, $input->value_names()), "Found self in the actor listing");
+
+sub login {
+ my $agent = shift;
+
+ my $url = "http://localhost:" . RT->Config->Get('WebPort') . RT->Config->Get('WebPath') . "/";
+ $agent->get($url);
+ is( $agent->{'status'}, 200,
+ "Loaded a page - http://localhost" . RT->Config->Get('WebPath') );
+
+ # {{{ test a login
+
+ # follow the link marked "Login"
+
+ ok( $agent->{form}->find_input('user') );
+
+ ok( $agent->{form}->find_input('pass') );
+ like( $agent->{'content'} , qr/username:/i );
+ $agent->field( 'user' => $user_obj->Name );
+ $agent->field( 'pass' => 'customer' );
+
+ # the field isn't named, so we have to click link 0
+ $agent->click(0);
+ is( $agent->{'status'}, 200, "Fetched the page ok" );
+ like( $agent->{'content'} , qr/Logout/i, "Found a logout link" );
+}
+1;
diff --git a/rt/t/web/saved_search_chart.t b/rt/t/web/saved_search_chart.t
new file mode 100644
index 000000000..105166233
--- /dev/null
+++ b/rt/t/web/saved_search_chart.t
@@ -0,0 +1,86 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 19;
+my ( $url, $m ) = RT::Test->started_ok;
+use RT::Attribute;
+my $search = RT::Attribute->new($RT::SystemUser);
+my $ticket = RT::Ticket->new($RT::SystemUser);
+my ( $ret, $msg ) = $ticket->Create(
+ Subject => 'base ticket' . $$,
+ Queue => 'general',
+ Owner => 'root',
+ Requestor => 'root@localhost',
+ MIMEObj => MIME::Entity->build(
+ From => 'root@localhost',
+ To => 'rt@localhost',
+ Subject => 'base ticket' . $$,
+ Data => "",
+ ),
+);
+ok( $ret, "ticket created: $msg" );
+
+ok( $m->login, 'logged in' );
+
+$m->get_ok( $url . "/Search/Chart.html?Query=" . 'id=1' );
+my ($owner) = $m->content =~ /value="(RT::User-\d+)"/;
+
+$m->submit_form(
+ form_name => 'SaveSearch',
+ fields => {
+ SavedSearchDescription => 'first chart',
+ SavedSearchOwner => $owner,
+ },
+ button => 'SavedSearchSave',
+);
+
+$m->content_like( qr/Chart first chart saved/, 'saved first chart' );
+
+my ( $search_uri, $id ) = $m->content =~ /value="(RT::User-\d+-SavedSearch-(\d+))"/;
+$m->submit_form(
+ form_name => 'SaveSearch',
+ fields => { SavedSearchLoad => $search_uri },
+);
+
+$m->content_like( qr/name="SavedSearchDelete"\s+value="Delete"/,
+ 'found Delete button' );
+$m->content_like(
+ qr/name="SavedSearchDescription"\s+value="first chart"/,
+ 'found Description input with the value filled'
+);
+$m->content_like( qr/name="SavedSearchSave"\s+value="Update"/,
+ 'found Update button' );
+$m->content_unlike( qr/name="SavedSearchSave"\s+value="Save"/,
+ 'no Save button' );
+
+$m->submit_form(
+ form_name => 'SaveSearch',
+ fields => {
+ Query => 'id=2',
+ PrimaryGroupBy => 'Status',
+ ChartStyle => 'pie',
+ },
+ button => 'SavedSearchSave',
+);
+
+$m->content_like( qr/Chart first chart updated/, 'found updated message' );
+$m->content_like( qr/id=2/, 'Query is updated' );
+$m->content_like( qr/value="Status"\s+selected="selected"/,
+ 'PrimaryGroupBy is updated' );
+$m->content_like( qr/value="pie"\s+selected="selected"/,
+ 'ChartType is updated' );
+ok( $search->Load($id) );
+is( $search->SubValue('Query'), 'id=2', 'Query is indeed updated' );
+is( $search->SubValue('PrimaryGroupBy'),
+ 'Status', 'PrimaryGroupBy is indeed updated' );
+is( $search->SubValue('ChartStyle'), 'pie', 'ChartStyle is indeed updated' );
+
+# finally, let's test delete
+$m->submit_form(
+ form_name => 'SaveSearch',
+ button => 'SavedSearchDelete',
+);
+$m->content_like( qr/Chart first chart deleted/, 'found deleted message' );
+$m->content_unlike( qr/value="RT::User-\d+-SavedSearch-\d+"/,
+ 'no saved search' );
diff --git a/rt/t/web/saved_search_permissions.t b/rt/t/web/saved_search_permissions.t
new file mode 100644
index 000000000..f91ca13c6
--- /dev/null
+++ b/rt/t/web/saved_search_permissions.t
@@ -0,0 +1,34 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 10;
+my $user = RT::User->new($RT::SystemUser);
+ok(
+ $user->Create(
+ Name => 'foo',
+ Privileged => 1,
+ Password => 'foobar'
+ )
+);
+
+my ( $url, $m ) = RT::Test->started_ok;
+ok( $m->login, 'root logged in' );
+$m->get_ok( $url . '/Search/Build.html?Query=id<100' );
+$m->submit_form(
+ form_name => 'BuildQuery',
+ fields => { SavedSearchDescription => 'test' },
+ button => 'SavedSearchSave',
+);
+$m->content_contains( q{name="SavedSearchDescription" value="test"},
+ 'saved test search' );
+my ($id) = $m->content =~ /value="(RT::User-\d+-SavedSearch-\d+)"/;
+ok( $m->login( 'foo', 'foobar' ), 'logged in' );
+$m->get_ok( $url . "/Search/Build.html?SavedSearchLoad=$id" );
+
+my $message = qq{Can not load saved search "$id"};
+RT::Interface::Web::EscapeUTF8( \$message );
+$m->content_contains( $message, 'user foo can not load saved search of root' );
+
+$m->warning_like( qr/User #\d+ tried to load container user #\d+/,
+ 'get warning' );
diff --git a/rt/t/web/search_bulk_update_links.t b/rt/t/web/search_bulk_update_links.t
new file mode 100644
index 000000000..d6bfdfd3c
--- /dev/null
+++ b/rt/t/web/search_bulk_update_links.t
@@ -0,0 +1,147 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 28;
+my ( $url, $m ) = RT::Test->started_ok;
+ok( $m->login, 'logged in' );
+
+my $rtname = RT->Config->Get('rtname');
+
+# create tickets
+use RT::Ticket;
+
+my ( @link_tickets, @search_tickets );
+for ( 1 .. 3 ) {
+ my $link_ticket = RT::Ticket->new($RT::SystemUser);
+ my ( $ret, $msg ) = $link_ticket->Create(
+ Subject => "link ticket $_",
+ Queue => 'general',
+ Owner => 'root',
+ Requestor => 'root@localhost',
+ );
+ ok( $ret, "link ticket created: $msg" );
+ push @link_tickets, $ret;
+}
+
+for ( 1 .. 3 ) {
+ my $ticket = RT::Ticket->new($RT::SystemUser);
+ my ( $ret, $msg ) = $ticket->Create(
+ Subject => "search ticket $_",
+ Queue => 'general',
+ Owner => 'root',
+ Requestor => 'root@localhost',
+ );
+ ok( $ret, "search ticket created: $msg" );
+ push @search_tickets, $ret;
+}
+
+# let's add link to 1 search ticket first
+$m->get_ok( $url . "/Search/Bulk.html?Query=id=$search_tickets[0]&Rows=10" );
+$m->content_contains( 'Current Links', 'has current links part' );
+$m->content_lacks( 'DeleteLink--', 'no delete link stuff' );
+$m->submit_form(
+ form_number => 3,
+ fields => {
+ 'Ticket-DependsOn' => $link_tickets[0],
+ 'Ticket-MemberOf' => $link_tickets[1],
+ 'Ticket-RefersTo' => $link_tickets[2],
+ },
+);
+$m->content_contains(
+ "Ticket $search_tickets[0] depends on Ticket $link_tickets[0]",
+ 'depends on msg',
+);
+$m->content_contains(
+ "Ticket $search_tickets[0] member of Ticket $link_tickets[1]",
+ 'member of msg',
+);
+$m->content_contains(
+ "Ticket $search_tickets[0] refers to Ticket $link_tickets[2]",
+ 'refers to msg',
+);
+
+$m->content_contains(
+ "DeleteLink--DependsOn-fsck.com-rt://$rtname/ticket/$link_tickets[0]",
+ 'found depends on link' );
+$m->content_contains(
+ "DeleteLink--MemberOf-fsck.com-rt://$rtname/ticket/$link_tickets[1]",
+ 'found member of link' );
+$m->content_contains(
+ "DeleteLink--RefersTo-fsck.com-rt://$rtname/ticket/$link_tickets[2]",
+ 'found refers to link' );
+
+# here we check the *real* bulk update
+my $query = join ' OR ', map { "id=$_" } @search_tickets;
+$m->get_ok( $url . "/Search/Bulk.html?Query=$query&Rows=10" );
+$m->content_contains( 'Current Links', 'has current links part' );
+$m->content_lacks( 'DeleteLink--', 'no delete link stuff' );
+
+# test DependsOn, MemberOf and RefersTo
+$m->submit_form(
+ form_number => 3,
+ fields => {
+ 'Ticket-DependsOn' => $link_tickets[0],
+ 'Ticket-MemberOf' => $link_tickets[1],
+ 'Ticket-RefersTo' => $link_tickets[2],
+ },
+);
+
+$m->content_contains(
+ "DeleteLink--DependsOn-fsck.com-rt://$rtname/ticket/$link_tickets[0]",
+ 'found depends on link' );
+$m->content_contains(
+ "DeleteLink--MemberOf-fsck.com-rt://$rtname/ticket/$link_tickets[1]",
+ 'found member of link' );
+$m->content_contains(
+ "DeleteLink--RefersTo-fsck.com-rt://$rtname/ticket/$link_tickets[2]",
+ 'found refers to link' );
+
+$m->submit_form(
+ form_number => 3,
+ fields => {
+ "DeleteLink--DependsOn-fsck.com-rt://$rtname/ticket/$link_tickets[0]" =>
+ 1,
+ "DeleteLink--MemberOf-fsck.com-rt://$rtname/ticket/$link_tickets[1]" =>
+ 1,
+ "DeleteLink--RefersTo-fsck.com-rt://$rtname/ticket/$link_tickets[2]" =>
+ 1,
+ },
+);
+
+$m->content_lacks( 'DeleteLink--', 'links are all deleted' );
+
+# test DependedOnBy, Members and ReferredToBy
+
+$m->submit_form(
+ form_number => 3,
+ fields => {
+ 'DependsOn-Ticket' => $link_tickets[0],
+ 'MemberOf-Ticket' => $link_tickets[1],
+ 'RefersTo-Ticket' => $link_tickets[2],
+ },
+);
+
+$m->content_contains(
+ "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[0]-DependsOn-",
+ 'found depended on link' );
+$m->content_contains(
+ "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[1]-MemberOf-",
+ 'found members link' );
+$m->content_contains(
+ "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[2]-RefersTo-",
+ 'found referrd to link' );
+
+$m->submit_form(
+ form_number => 3,
+ fields => {
+ "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[0]-DependsOn-" =>
+ 1,
+ "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[1]-MemberOf-" =>
+ 1,
+ "DeleteLink-fsck.com-rt://$rtname/ticket/$link_tickets[2]-RefersTo-" =>
+ 1,
+ },
+);
+$m->content_lacks( 'DeleteLink--', 'links are all deleted' );
+
diff --git a/rt/t/web/ticket-create-utf8.t b/rt/t/web/ticket-create-utf8.t
new file mode 100644
index 000000000..a4d7ae98d
--- /dev/null
+++ b/rt/t/web/ticket-create-utf8.t
@@ -0,0 +1,83 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use RT::Test tests => 14;
+
+$RT::Test::SKIP_REQUEST_WORK_AROUND = 1;
+
+use Encode;
+
+my $ru_test = "\x{442}\x{435}\x{441}\x{442}";
+my $ru_autoreply = "\x{410}\x{432}\x{442}\x{43e}\x{43e}\x{442}\x{432}\x{435}\x{442}";
+my $ru_support = "\x{43f}\x{43e}\x{434}\x{434}\x{435}\x{440}\x{436}\x{43a}\x{430}";
+
+my $q = RT::Test->load_or_create_queue( Name => 'Regression' );
+ok $q && $q->id, 'loaded or created queue';
+
+RT::Test->set_rights(
+ Principal => 'Everyone',
+ Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
+);
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'logged in';
+
+# create a ticket with a subject only
+{
+ ok $m->goto_create_ticket( $q ), "go to create ticket";
+ $m->form_number(3);
+ $m->field( Subject => $ru_test );
+ $m->submit;
+
+ $m->content_like(
+ qr{<td\s+class="message-header-value"[^>]*>\s*\Q$ru_test\E\s*</td>}i,
+ 'header on the page'
+ );
+
+ my $ticket = RT::Test->last_ticket;
+ is $ticket->Subject, $ru_test, "correct subject";
+}
+
+# create a ticket with a subject and content
+{
+ ok $m->goto_create_ticket( $q ), "go to create ticket";
+ $m->form_number(3);
+ $m->field( Subject => $ru_test );
+ $m->field( Content => $ru_support );
+ $m->submit;
+
+ $m->content_like(
+ qr{<td\s+class="message-header-value"[^>]*>\s*\Q$ru_test\E\s*</td>}i,
+ 'header on the page'
+ );
+ $m->content_like(
+ qr{\Q$ru_support\E}i,
+ 'content on the page'
+ );
+
+ my $ticket = RT::Test->last_ticket;
+ is $ticket->Subject, $ru_test, "correct subject";
+}
+
+# create a ticket with a subject and content
+{
+ ok $m->goto_create_ticket( $q ), "go to create ticket";
+ $m->form_number(3);
+ $m->field( Subject => $ru_test );
+ $m->field( Content => $ru_support );
+ $m->submit;
+
+ $m->content_like(
+ qr{<td\s+class="message-header-value"[^>]*>\s*\Q$ru_test\E\s*</td>}i,
+ 'header on the page'
+ );
+ $m->content_like(
+ qr{\Q$ru_support\E}i,
+ 'content on the page'
+ );
+
+ my $ticket = RT::Test->last_ticket;
+ is $ticket->Subject, $ru_test, "correct subject";
+}
diff --git a/rt/t/web/ticket_owner.t b/rt/t/web/ticket_owner.t
new file mode 100644
index 000000000..0bacaf1bc
--- /dev/null
+++ b/rt/t/web/ticket_owner.t
@@ -0,0 +1,356 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use RT::Test tests => 91;
+
+my $queue = RT::Test->load_or_create_queue( Name => 'Regression' );
+ok $queue && $queue->id, 'loaded or created queue';
+
+my $user_a = RT::Test->load_or_create_user(
+ Name => 'user_a', Password => 'password',
+);
+ok $user_a && $user_a->id, 'loaded or created user';
+
+my $user_b = RT::Test->load_or_create_user(
+ Name => 'user_b', Password => 'password',
+);
+ok $user_b && $user_b->id, 'loaded or created user';
+
+RT::Test->started_ok;
+
+ok( RT::Test->set_rights(
+ { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket ReplyToTicket)] },
+ { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] },
+), 'set rights');
+
+my $agent_a = RT::Test::Web->new;
+ok $agent_a->login('user_a', 'password'), 'logged in as user A';
+
+diag "current user has no right to own, nobody selected as owner on create" if $ENV{TEST_VERBOSE};
+{
+ $agent_a->get_ok('/', 'open home page');
+ $agent_a->form_name('CreateTicketInQueue');
+ $agent_a->select( 'Queue', $queue->id );
+ $agent_a->submit;
+
+ $agent_a->content_like(qr/Create a new ticket/i, 'opened create ticket page');
+ my $form = $agent_a->form_name('TicketCreate');
+ is $form->value('Owner'), $RT::Nobody->id, 'correct owner selected';
+ ok !grep($_ == $user_a->id, $form->find_input('Owner')->possible_values),
+ 'user A can not own tickets';
+ $agent_a->submit;
+
+ $agent_a->content_like(qr/Ticket \d+ created in queue/i, 'created ticket');
+ my ($id) = ($agent_a->content =~ /Ticket (\d+) created in queue/);
+ ok $id, 'found id of the ticket';
+
+ my $ticket = RT::Ticket->new( $RT::SystemUser );
+ $ticket->Load( $id );
+ ok $ticket->id, 'loaded the ticket';
+ is $ticket->Owner, $RT::Nobody->id, 'correct owner';
+}
+
+diag "user can chose owner of a new ticket" if $ENV{TEST_VERBOSE};
+{
+ $agent_a->get_ok('/', 'open home page');
+ $agent_a->form_name('CreateTicketInQueue');
+ $agent_a->select( 'Queue', $queue->id );
+ $agent_a->submit;
+
+ $agent_a->content_like(qr/Create a new ticket/i, 'opened create ticket page');
+ my $form = $agent_a->form_name('TicketCreate');
+ is $form->value('Owner'), $RT::Nobody->id, 'correct owner selected';
+
+ ok grep($_ == $user_b->id, $form->find_input('Owner')->possible_values),
+ 'user B is listed as potential owner';
+ $agent_a->select('Owner', $user_b->id);
+ $agent_a->submit;
+
+ $agent_a->content_like(qr/Ticket \d+ created in queue/i, 'created ticket');
+ my ($id) = ($agent_a->content =~ /Ticket (\d+) created in queue/);
+ ok $id, 'found id of the ticket';
+
+ my $ticket = RT::Ticket->new( $RT::SystemUser );
+ $ticket->Load( $id );
+ ok $ticket->id, 'loaded the ticket';
+ is $ticket->Owner, $user_b->id, 'correct owner';
+}
+
+my $agent_b = RT::Test::Web->new;
+ok $agent_b->login('user_b', 'password'), 'logged in as user B';
+
+diag "user A can not change owner after create" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Owner => $user_b->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $user_b->id, 'correct owner';
+
+ # try the following group of tests twice with different agents(logins)
+ my $test_cb = sub {
+ my $agent = shift;
+ $agent->goto_ticket( $id );
+ $agent->follow_link_ok({text => 'Basics'}, 'Ticket -> Basics');
+ my $form = $agent->form_number(3);
+ is $form->value('Owner'), $user_b->id, 'correct owner selected';
+ $agent->select('Owner', $RT::Nobody->id);
+ $agent->submit;
+
+ $agent->content_like(
+ qr/Permission denied/i,
+ 'no way to change owner after create if you have no rights'
+ );
+
+ my $ticket = RT::Ticket->new( $RT::SystemUser );
+ $ticket->Load( $id );
+ ok $ticket->id, 'loaded the ticket';
+ is $ticket->Owner, $user_b->id, 'correct owner';
+ };
+
+ $test_cb->($agent_a);
+ diag "even owner(user B) can not change owner" if $ENV{TEST_VERBOSE};
+ $test_cb->($agent_b);
+}
+
+diag "on reply correct owner is selected" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Owner => $user_b->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $user_b->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ $agent_a->follow_link_ok({text => 'Reply'}, 'Ticket -> Basics');
+
+ my $form = $agent_a->form_number(3);
+ is $form->value('Owner'), '', 'empty value selected';
+ $agent_a->submit;
+
+ $ticket = RT::Ticket->new( $RT::SystemUser );
+ $ticket->Load( $id );
+ ok $ticket->id, 'loaded the ticket';
+ is $ticket->Owner, $user_b->id, 'correct owner';
+}
+
+ok( RT::Test->set_rights(
+ { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket)] },
+ { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] },
+), 'set rights');
+
+diag "Couldn't take without coresponding right" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $RT::Nobody->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ ok !($agent_a->find_all_links( text => 'Take' ))[0],
+ 'no Take link';
+ ok !($agent_a->find_all_links( text => 'Steal' ))[0],
+ 'no Steal link as well';
+}
+
+diag "Couldn't steal without coresponding right" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Owner => $user_b->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $user_b->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ ok !($agent_a->find_all_links( text => 'Steal' ))[0],
+ 'no Steal link';
+ ok !($agent_a->find_all_links( text => 'Take' ))[0],
+ 'no Take link as well';
+}
+
+ok( RT::Test->set_rights(
+ { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket TakeTicket)] },
+), 'set rights');
+
+diag "TakeTicket require OwnTicket to work" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $RT::Nobody->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ ok !($agent_a->find_all_links( text => 'Take' ))[0],
+ 'no Take link';
+ ok !($agent_a->find_all_links( text => 'Steal' ))[0],
+ 'no Steal link as well';
+}
+
+ok( RT::Test->set_rights(
+ { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket TakeTicket)] },
+ { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] },
+), 'set rights');
+
+diag "TakeTicket+OwnTicket work" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $RT::Nobody->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ ok !($agent_a->find_all_links( text => 'Steal' ))[0],
+ 'no Steal link';
+ $agent_a->follow_link_ok({text => 'Take'}, 'Ticket -> Take');
+
+ $ticket = RT::Ticket->new( $RT::SystemUser );
+ $ticket->Load( $id );
+ ok $ticket->id, 'loaded the ticket';
+ is $ticket->Owner, $user_a->id, 'correct owner';
+}
+
+diag "TakeTicket+OwnTicket don't work when owner is not nobody" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Owner => $user_b->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $user_b->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ ok !($agent_a->find_all_links( text => 'Take' ))[0],
+ 'no Take link';
+ ok !($agent_a->find_all_links( text => 'Steal' ))[0],
+ 'no Steal link too';
+}
+
+ok( RT::Test->set_rights(
+ { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket StealTicket)] },
+ { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] },
+), 'set rights');
+
+diag "StealTicket require OwnTicket to work" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Owner => $user_b->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $user_b->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ ok !($agent_a->find_all_links( text => 'Steal' ))[0],
+ 'no Steal link';
+ ok !($agent_a->find_all_links( text => 'Take' ))[0],
+ 'no Take link too';
+}
+
+ok( RT::Test->set_rights(
+ { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket StealTicket)] },
+ { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] },
+), 'set rights');
+
+diag "StealTicket+OwnTicket work" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Owner => $user_b->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $user_b->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ ok !($agent_a->find_all_links( text => 'Take' ))[0],
+ 'but no Take link';
+ $agent_a->follow_link_ok({text => 'Steal'}, 'Ticket -> Steal');
+
+ $ticket = RT::Ticket->new( $RT::SystemUser );
+ $ticket->Load( $id );
+ ok $ticket->id, 'loaded the ticket';
+ is $ticket->Owner, $user_a->id, 'correct owner';
+}
+
+diag "StealTicket+OwnTicket don't work when owner is nobody" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $RT::Nobody->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ ok !($agent_a->find_all_links( text => 'Steal' ))[0],
+ 'no Steal link';
+ ok !($agent_a->find_all_links( text => 'Take' ))[0],
+ 'no Take link as well (no right)';
+}
+
+ok( RT::Test->set_rights(
+ { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket TakeTicket StealTicket)] },
+ { Principal => $user_b, Right => [qw(SeeQueue ShowTicket OwnTicket)] },
+), 'set rights');
+
+diag "no Steal link when owner nobody" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $RT::Nobody->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ ok !($agent_a->find_all_links( text => 'Steal' ))[0],
+ 'no Steal link';
+ ok( ($agent_a->find_all_links( text => 'Take' ))[0],
+ 'but have Take link');
+}
+
+diag "no Take link when owner is not nobody" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($id, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Owner => $user_b->id,
+ Subject => 'test',
+ );
+ ok $id, 'created a ticket #'. $id or diag "error: $msg";
+ is $ticket->Owner, $user_b->id, 'correct owner';
+
+ $agent_a->goto_ticket( $id );
+ ok !($agent_a->find_all_links( text => 'Take' ))[0],
+ 'no Take link';
+ ok( ($agent_a->find_all_links( text => 'Steal' ))[0],
+ 'but have Steal link');
+}
+
diff --git a/rt/t/web/ticket_seen.t b/rt/t/web/ticket_seen.t
new file mode 100644
index 000000000..00b2632d8
--- /dev/null
+++ b/rt/t/web/ticket_seen.t
@@ -0,0 +1,80 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use RT::Test tests => 16;
+
+my $queue = RT::Test->load_or_create_queue( Name => 'Regression' );
+ok $queue && $queue->id, 'loaded or created queue';
+
+my $user_a = RT::Test->load_or_create_user(
+ Name => 'user_a', Password => 'password',
+);
+ok $user_a && $user_a->id, 'loaded or created user';
+
+my $user_b = RT::Test->load_or_create_user(
+ Name => 'user_b', Password => 'password',
+);
+ok $user_b && $user_b->id, 'loaded or created user';
+
+ok( RT::Test->set_rights(
+ { Principal => $user_a, Right => [qw(SeeQueue ShowTicket CreateTicket OwnTicket ModifyTicket)] },
+ { Principal => $user_b, Right => [qw(SeeQueue ShowTicket ReplyToTicket)] },
+), 'set rights');
+RT::Test->started_ok;
+
+my $agent_a = RT::Test::Web->new;
+ok $agent_a->login('user_a', 'password'), 'logged in as user A';
+
+my $agent_b = RT::Test::Web->new;
+ok $agent_b->login('user_b', 'password'), 'logged in as user B';
+
+diag "create a ticket for testing" if $ENV{TEST_VERBOSE};
+my $tid;
+{
+ my $ticket = RT::Ticket->new( $user_a );
+ my ($txn, $msg);
+ ($tid, $txn, $msg) = $ticket->Create(
+ Queue => $queue->id,
+ Owner => $user_a->id,
+ Subject => 'test',
+ );
+ ok $tid, 'created a ticket #'. $tid or diag "error: $msg";
+ is $ticket->Owner, $user_a->id, 'correct owner';
+}
+
+diag "user B adds a message, we check that user A see notification and can clear it" if $ENV{TEST_VERBOSE};
+{
+ my $ticket = RT::Ticket->new( $user_b );
+ $ticket->Load( $tid );
+ ok $ticket->id, 'loaded the ticket';
+
+ my ($status, $msg) = $ticket->Correspond( Content => 'bla-bla' );
+ ok $status, 'added reply' or diag "error: $msg";
+
+ $agent_a->goto_ticket($tid);
+ $agent_a->content_like(qr/bla-bla/ims, 'the message on the page');
+
+ $agent_a->content_like(
+ qr/unread message/ims,
+ 'we have not seen something'
+ );
+
+ $agent_a->follow_link_ok({text => 'jump to the first unread message and mark all messages as seen'}, 'try to mark all as seen');
+ $agent_a->content_like(
+ qr/Marked all messages as seen/ims,
+ 'see success message'
+ );
+
+ $agent_a->goto_ticket($tid);
+ $agent_a->content_unlike(
+ qr/unread message/ims,
+ 'we have seen everything, so no messages'
+ );
+}
+
+
+
+
+
diff --git a/rt/t/web/ticket_update_without_content.t b/rt/t/web/ticket_update_without_content.t
new file mode 100644
index 000000000..595cb74e9
--- /dev/null
+++ b/rt/t/web/ticket_update_without_content.t
@@ -0,0 +1,52 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use RT::Test tests => 10;
+my ( $url, $m ) = RT::Test->started_ok;
+
+# merged tickets still show up in search
+my $ticket = RT::Ticket->new($RT::SystemUser);
+my ( $ret, $msg ) = $ticket->Create(
+ Subject => 'base ticket' . $$,
+ Queue => 'general',
+ Owner => 'root',
+ Requestor => 'root@localhost',
+ MIMEObj => MIME::Entity->build(
+ From => 'root@localhost',
+ To => 'rt@localhost',
+ Subject => 'base ticket' . $$,
+ Data => "",
+ ),
+);
+ok( $ret, "ticket created: $msg" );
+
+ok( $m->login, 'logged in' );
+
+$m->get_ok( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id );
+
+$m->submit_form(
+ form_number => 3,
+ fields => { Priority => '1', }
+);
+
+$m->content_like(qr/priority changed/i);
+$m->content_unlike(qr/message recorded/i);
+
+my $root = RT::User->new( $RT::SystemUser );
+$root->Load('root');
+( $ret, $msg ) = $root->SetSignature(<<EOF);
+best wishes
+foo
+EOF
+
+ok( $ret, $msg );
+
+$m->get_ok( $url . "/Ticket/ModifyAll.html?id=" . $ticket->id );
+
+$m->submit_form(
+ form_number => 3,
+ fields => { Priority => '2', }
+);
+$m->content_like(qr/priority changed/i);
+$m->content_unlike(qr/message recorded/i);
diff --git a/rt/t/web/unlimited_search.t b/rt/t/web/unlimited_search.t
new file mode 100644
index 000000000..d98baaac0
--- /dev/null
+++ b/rt/t/web/unlimited_search.t
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+
+use strict;
+
+use RT::Test tests => 8;
+RT::Test->started_ok;
+
+my $ticket = RT::Ticket->new($RT::SystemUser);
+for ( 1 .. 75 ) {
+ $ticket->Create(
+ Subject => 'Ticket ' . $_,
+ Queue => 'General',
+ Owner => 'root',
+ Requestor => 'unlimitedsearch@localhost',
+ );
+}
+
+my $agent = RT::Test::Web->new;
+ok $agent->login('root', 'password'), 'logged in as root';
+
+$agent->get_ok('/Search/Build.html');
+$agent->form_name('BuildQuery');
+$agent->field('idOp', '>');
+$agent->field('ValueOfid', '0');
+$agent->submit('AddClause');
+$agent->form_name('BuildQuery');
+$agent->field('RowsPerPage', '0');
+$agent->submit('DoSearch');
+$agent->follow_link_ok({text=>'Show Results'});
+$agent->content_like(qr/Ticket 75/);
+
+$agent->follow_link_ok({text=>'New Search'});
+$agent->form_name('BuildQuery');
+$agent->field('idOp', '>');
+$agent->field('ValueOfid', '0');
+$agent->submit('AddClause');
+$agent->form_name('BuildQuery');
+$agent->field('RowsPerPage', '50');
+$agent->submit('DoSearch');
+$agent->follow_link_ok({text=>'Bulk Update'});
+$agent->content_unlike(qr/Ticket 51/);