Commit 9b6ec1f5 authored by David Lawrence's avatar David Lawrence

Bug 1069799 - move the QA repository into the main repository

r=LpSolit
parent 6f70920f
...@@ -41,11 +41,6 @@ if [ "$TEST_SUITE" = "docs" ]; then ...@@ -41,11 +41,6 @@ if [ "$TEST_SUITE" = "docs" ]; then
exit $? exit $?
fi fi
echo -e "\n== Cloning QA test suite"
cd $BUGZILLA_ROOT
echo "Cloning git repo $GITHUB_QA_GIT branch $GITHUB_BASE_BRANCH ..."
git clone $GITHUB_QA_GIT -b $GITHUB_BASE_BRANCH qa
echo -e "\n== Starting database" echo -e "\n== Starting database"
/usr/bin/mysqld_safe & /usr/bin/mysqld_safe &
sleep 3 sleep 3
...@@ -55,32 +50,24 @@ echo -e "\n== Starting memcached" ...@@ -55,32 +50,24 @@ echo -e "\n== Starting memcached"
sleep 3 sleep 3
echo -e "\n== Updating configuration" echo -e "\n== Updating configuration"
sed -e "s?%DB%?$BUGS_DB_DRIVER?g" --in-place qa/config/checksetup_answers.txt sed -e "s?%DB%?$BUGS_DB_DRIVER?g" --in-place xt/config/checksetup_answers.txt
sed -e "s?%DB_NAME%?bugs_test?g" --in-place qa/config/checksetup_answers.txt echo "\$answer{'memcached_servers'} = 'localhost:11211';" >> xt/config/checksetup_answers.txt
sed -e "s?%USER%?$USER?g" --in-place qa/config/checksetup_answers.txt
sed -e "s?%TRAVIS_BUILD_DIR%?$BUGZILLA_ROOT?g" --in-place qa/config/selenium_test.conf
echo "\$answer{'memcached_servers'} = 'localhost:11211';" >> qa/config/checksetup_answers.txt
if [ "$TEST_SUITE" == "checksetup" ]; then
cd $BUGZILLA_ROOT/qa
/bin/bash /docker_files/buildbot_step "Checksetup" ./test_checksetup.pl config/config-checksetup-$BUGS_DB_DRIVER
exit $?
fi
echo -e "\n== Running checksetup" echo -e "\n== Running checksetup"
cd $BUGZILLA_ROOT cd $BUGZILLA_ROOT
./checksetup.pl qa/config/checksetup_answers.txt ./checksetup.pl xt/config/checksetup_answers.txt
./checksetup.pl qa/config/checksetup_answers.txt ./checksetup.pl xt/config/checksetup_answers.txt
echo -e "\n== Generating test data" echo -e "\n== Generating test data"
cd $BUGZILLA_ROOT/qa/config cd $BUGZILLA_ROOT/xt/config
perl -I../../local/lib/perl5 generate_test_data.pl perl generate_test_data.pl
echo -e "\n== Starting web server" echo -e "\n== Starting web server"
sed -e "s?^#Perl?Perl?" --in-place /etc/httpd/conf.d/bugzilla.conf sed -e "s?^#Perl?Perl?" --in-place /etc/httpd/conf.d/bugzilla.conf
/usr/sbin/httpd & /usr/sbin/httpd &
sleep 3 sleep 3
cd $BUGZILLA_ROOT
if [ "$TEST_SUITE" = "selenium" ]; then if [ "$TEST_SUITE" = "selenium" ]; then
export DISPLAY=:0 export DISPLAY=:0
...@@ -100,13 +87,11 @@ if [ "$TEST_SUITE" = "selenium" ]; then ...@@ -100,13 +87,11 @@ if [ "$TEST_SUITE" = "selenium" ]; then
# but no tests actually executed. # but no tests actually executed.
[ $NO_TESTS ] && exit 0 [ $NO_TESTS ] && exit 0
cd $BUGZILLA_ROOT/qa/t /bin/bash /docker_files/buildbot_step "Selenium" prove -f -v xt/selenium/*.t
/bin/bash /docker_files/buildbot_step "Selenium" prove -f -v -I$BUGZILLA_ROOT/lib test_*.t
exit $? exit $?
fi fi
if [ "$TEST_SUITE" = "webservices" ]; then if [ "$TEST_SUITE" = "webservices" ]; then
cd $BUGZILLA_ROOT/qa/t /bin/bash /docker_files/buildbot_step "Webservices" prove -f -v xt/{rest,webservice}/*.t
/bin/bash /docker_files/buildbot_step "Webservices" prove -f -v -I$BUGZILLA_ROOT/lib {rest,webservice}_*.t
exit $? exit $?
fi fi
...@@ -28,7 +28,7 @@ foreach my $extension (@extensions) { ...@@ -28,7 +28,7 @@ foreach my $extension (@extensions) {
find(sub { push(@files, $File::Find::name) if $_ =~ /\.pm$/;}, $extension); find(sub { push(@files, $File::Find::name) if $_ =~ /\.pm$/;}, $extension);
} }
our @test_files = glob('t/*.t'); our @test_files = glob('t/*.t xt/*/*.t');
sub isTestingFile { sub isTestingFile {
my ($file) = @_; my ($file) = @_;
......
The tests in this directory require a working database, as opposed
to the tests in t/, which simply test the code without a working
installation.
Some of the tests may modify your current working installation, even
if only temporarily. To run the tests that modify your database,
set the environment variable BZ_WRITE_TESTS to 1.
Some tests also take additional, optional arguments. You can pass arguments
to tests like:
prove xt/search.t :: --long --operators=equals,notequals
Note the "::"--that is necessary to note that the arguments are going to
the test, not to "prove".
See the perldoc of the individual tests to see what options they support,
or do "perl xt/search.t --help".
$answer{'db_host'} = 'localhost';
$answer{'db_driver'} = '%DB%';
$answer{'db_port'} = 0;
$answer{'db_name'} = 'bugs_test',
$answer{'db_user'} = 'bugs';
$answer{'db_pass'} = 'bugs';
$answer{'db_sock'} = '';
$answer{'db_check'} = 1;
$answer{'db_mysql_ssl_ca_file'} = '';
$answer{'db_mysql_ssl_ca_path'} = '';
$answer{'db_mysql_ssl_client_cert'} = '';
$answer{'db_mysql_ssl_client_key'} = '';
$answer{'urlbase'} = 'http://localhost/bugzilla/';
$answer{'create_htaccess'} = '';
$answer{'use_suexec'} = '';
$answer{'index_html'} = 0;
$answer{'cvsbin'} = '/usr/bin/cvs';
$answer{'interdiffbin'} = '/usr/bin/interdiff';
$answer{'diffpath'} = '/usr/bin';
$answer{'webservergroup'} = 'bugzilla';
$answer{'ADMIN_OK'} = 'Y';
$answer{'ADMIN_EMAIL'} = 'admin@bugzilla.org';
$answer{'ADMIN_PASSWORD'} = 'password';
$answer{'ADMIN_REALNAME'} = 'QA Admin';
$answer{'NO_PAUSE'} = 1;
#!/usr/bin/perl
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# -*- Mode: perl; indent-tabs-mode: nil -*-
use 5.10.1;
use strict;
use warnings;
use Cwd;
my $conf_path;
my $config;
BEGIN {
say 'reading the config file...';
my $conf_file = 'selenium_test.conf';
$config = do "$conf_file"
or die "can't read configuration '$conf_file': $!$@";
$conf_path = $config->{bugzilla_path};
# We don't want randomly-generated keys. We want the ones specified
# in the config file so that we can use them in tests scripts.
*Bugzilla::User::APIKey::_check_api_key = sub { return $_[1]; };
}
use lib $conf_path;
use Bugzilla;
use Bugzilla::Attachment;
use Bugzilla::Bug;
use Bugzilla::User;
use Bugzilla::Install;
use Bugzilla::Milestone;
use Bugzilla::Product;
use Bugzilla::Component;
use Bugzilla::Group;
use Bugzilla::Version;
use Bugzilla::Constants;
use Bugzilla::Keyword;
use Bugzilla::Config qw(:admin);
use Bugzilla::User::Setting;
use Bugzilla::User::APIKey;
my $dbh = Bugzilla->dbh;
# set Bugzilla usage mode to USAGE_MODE_CMDLINE
Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
##########################################################################
# Set Parameters
##########################################################################
# Some parameters must be turned on to create bugs requiring them.
# They are also expected to be turned on by some webservice_*.t scripts.
my ($urlbase, $sslbase);
$urlbase = $config->{browser_url} . '/' . $config->{bugzilla_installation};
$urlbase .= '/' unless $urlbase =~ /\/$/;
if ($urlbase =~ /^https/) {
$sslbase = $urlbase;
$urlbase =~ s/^https(.+)$/http$1/;
}
my %set_params = (
urlbase => $urlbase,
sslbase => $sslbase,
useqacontact => 1,
mail_delivery_method => 'Test',
maxattachmentsize => 256,
);
my $params_modified;
foreach my $param (keys %set_params) {
my $value = $set_params{$param};
next unless defined $value && Bugzilla->params->{$param} ne $value;
SetParam($param, $value);
$params_modified = 1;
}
write_params() if $params_modified;
##########################################################################
# Set Default User Preferences
##########################################################################
# When editing a bug, the page being displayed depends on the
# post_bug_submit_action user pref. We set it globally so that we know
# the exact behavior of process_bug.cgi.
my %user_prefs = (post_bug_submit_action => 'nothing');
foreach my $pref (keys %user_prefs) {
my $value = $user_prefs{$pref};
Bugzilla::User::Setting::set_default($pref, $value, 0);
}
##########################################################################
# Create Users
##########################################################################
# First of all, remove the default .* regexp for the editbugs group.
my $group = Bugzilla::Group->new({ name => 'editbugs' });
$group->set_user_regexp('');
$group->update();
my @usernames = (
'admin', 'no-privs',
'QA-Selenium-TEST', 'canconfirm',
'tweakparams', 'permanent_user',
'editbugs', 'disabled',
);
say 'creating user accounts...';
foreach my $username (@usernames) {
my ($password, $login);
my $prefix = $username;
if ($username eq 'permanent_user') {
$password = $config->{admin_user_passwd};
$login = $config->{$username};
}
elsif ($username eq 'no-privs') {
$prefix = 'unprivileged';
}
elsif ($username eq 'QA-Selenium-TEST') {
$prefix = 'QA_Selenium_TEST';
}
$password ||= $config->{"${prefix}_user_passwd"};
$login ||= $config->{"${prefix}_user_login"};
my $api_key = $config->{"${prefix}_user_api_key"};
if (is_available_username($login)) {
my %extra_args;
if ($username eq 'disabled') {
$extra_args{disabledtext} = '!!This is the text!!';
}
my $user = Bugzilla::User->create(
{ login_name => $login,
realname => $username,
cryptpassword => $password,
%extra_args,
}
);
if ($api_key) {
Bugzilla::User::APIKey->create(
{ user_id => $user->id,
description => 'API key for QA tests',
api_key => $api_key }
);
}
if ($username eq 'admin' or $username eq 'permanent_user') {
Bugzilla::Install::make_admin($login);
}
}
}
##########################################################################
# Create Bugs
##########################################################################
# login to bugzilla
my $admin_user = Bugzilla::User->check($config->{admin_user_login});
Bugzilla->set_user($admin_user);
my %field_values = (
'priority' => 'Highest',
'bug_status' => 'CONFIRMED',
'version' => 'unspecified',
'bug_file_loc' => '',
'comment' => 'please ignore this bug',
'component' => 'TestComponent',
'rep_platform' => 'All',
'short_desc' => 'This is a testing bug only',
'product' => 'TestProduct',
'op_sys' => 'Linux',
'bug_severity' => 'normal',
);
say 'creating bugs...';
my $bug = Bugzilla::Bug->create( \%field_values );
say 'Bug ' . $bug->id . ' created';
if (Bugzilla::Bug->new('public_bug')->{error}) {
# The deadline must be set so that this bug can be used to test
# timetracking fields using WebServices.
$bug = Bugzilla::Bug->create({ %field_values, alias => 'public_bug', deadline => '2010-01-01' });
say 'Bug ' . $bug->id . ' (alias: public_bug) created';
}
##########################################################################
# Create Classifications
##########################################################################
my @classifications = ({ name => 'Class2_QA',
description => "required by Selenium... DON'T DELETE" },
);
say 'creating classifications...';
for my $class (@classifications) {
my $new_class = Bugzilla::Classification->new({ name => $class->{name} });
if (!$new_class) {
$dbh->do('INSERT INTO classifications (name, description) VALUES (?, ?)',
undef, ($class->{name}, $class->{description}));
}
}
##########################################################################
# Create Products
##########################################################################
my @products = (
{ product_name => 'QA-Selenium-TEST',
description => "used by Selenium test.. DON'T DELETE",
versions => ['unspecified', 'QAVersion'],
milestones => ['QAMilestone'],
defaultmilestone => '---',
components => [
{ name => 'QA-Selenium-TEST',
description => "used by Selenium test.. DON'T DELETE",
initialowner => $config->{QA_Selenium_TEST_user_login},
initialqacontact => $config->{QA_Selenium_TEST_user_login},
initial_cc => [$config->{QA_Selenium_TEST_user_login}],
}
],
},
{ product_name => 'Another Product',
description => 'Alternate product used by Selenium. <b>Do not edit!</b>',
versions => ['unspecified', 'Another1', 'Another2'],
milestones => ['AnotherMS1', 'AnotherMS2', 'Milestone'],
defaultmilestone => '---',
components => [
{ name => 'c1',
description => 'c1',
initialowner => $config->{permanent_user},
initialqacontact => '',
initial_cc => [],
},
{ name => 'c2',
description => 'c2',
initialowner => $config->{permanent_user},
initialqacontact => '',
initial_cc => [],
},
],
},
{ product_name => 'C2 Forever',
description => 'I must remain in the Class2_QA classification ' .
'in all cases! Do not edit!',
classification => 'Class2_QA',
versions => ['unspecified', 'C2Ver'],
milestones => ['C2Mil'],
defaultmilestone => '---',
components => [
{ name => 'Helium',
description => 'Feel free to add bugs to me',
initialowner => $config->{permanent_user},
initialqacontact => '',
initial_cc => [],
}
],
},
{ product_name => 'QA Entry Only',
description => 'Only the QA group may enter bugs here.',
versions => ['unspecified'],
milestones => [],
defaultmilestone => '---',
components => [
{ name => 'c1',
description => "Same name as Another Product's component",
initialowner => $config->{QA_Selenium_TEST_user_login},
initialqacontact => '',
initial_cc => [],
}
],
},
{ product_name => 'QA Search Only',
description => 'Only the QA group may search for bugs here.',
versions => ['unspecified'],
milestones => [],
defaultmilestone => '---',
components => [
{ name => 'c1',
description => 'Still same name as the Another component',
initialowner => $config->{QA_Selenium_TEST_user_login},
initialqacontact => '',
initial_cc => [],
}
],
},
);
say 'creating products...';
foreach my $product (@products) {
my $new_product = Bugzilla::Product->new({ name => $product->{product_name} });
if (!$new_product) {
my $class_id = 1;
if ($product->{classification}) {
$class_id = Bugzilla::Classification->new({ name => $product->{classification} })->id;
}
$dbh->do('INSERT INTO products (name, description, classification_id) VALUES (?, ?, ?)',
undef, ($product->{product_name}, $product->{description}, $class_id));
$new_product = Bugzilla::Product->new({ name => $product->{product_name} });
$dbh->do('INSERT INTO milestones (product_id, value) VALUES (?, ?)',
undef, ($new_product->id, $product->{defaultmilestone} ));
# Now clear the internal list of accessible products.
delete Bugzilla->user->{selectable_products};
foreach my $component (@{ $product->{components} }) {
Bugzilla::Component->create(
{ name => $component->{name},
product => $new_product,
description => $component->{description},
initialowner => $component->{initialowner},
initialqacontact => $component->{initialqacontact},
initial_cc => $component->{initial_cc},
}
);
}
}
foreach my $version (@{ $product->{versions} }) {
my $new_version = Bugzilla::Version->new({ name => $version, product => $new_product });
if (!$new_version) {
Bugzilla::Version->create({ value => $version, product => $new_product });
}
}
foreach my $milestone (@{ $product->{milestones} }) {
my $new_milestone = Bugzilla::Milestone->new({ name => $milestone, product => $new_product });
if (!$new_milestone) {
# We don't use Bugzilla::Milestone->create because we want to
# bypass security checks.
$dbh->do('INSERT INTO milestones (product_id, value) VALUES (?,?)',
undef, $new_product->id, $milestone);
}
}
}
##########################################################################
# Create Groups
##########################################################################
# create Master group
my ($group_name, $group_desc) = ('Master', 'Master Selenium Group <b>DO NOT EDIT!</b>');
say 'creating groups...';
my $new_group = Bugzilla::Group->new({ name => $group_name });
if (!$new_group) {
my $group = Bugzilla::Group->create({ name => $group_name,
description => $group_desc,
isbuggroup => 1});
$dbh->do('INSERT INTO group_control_map
(group_id, product_id, entry, membercontrol, othercontrol, canedit)
SELECT ?, products.id, 0, ?, ?, 0 FROM products',
undef, ($group->id, CONTROLMAPSHOWN, CONTROLMAPSHOWN));
}
# create QA-Selenium-TEST group. Do not use Group->create() so that
# the admin group doesn't inherit membership (yes, that's what we want!).
($group_name, $group_desc) = ('QA-Selenium-TEST', "used by Selenium test.. DON'T DELETE");
$new_group = Bugzilla::Group->new({ name => $group_name });
if (!$new_group) {
$dbh->do('INSERT INTO groups (name, description, isbuggroup, isactive)
VALUES (?, ?, 1, 1)', undef, ($group_name, $group_desc));
}
##########################################################################
# Add Users to Groups
##########################################################################
my @users_groups = (
{ user => $config->{QA_Selenium_TEST_user_login}, group => 'QA-Selenium-TEST' },
{ user => $config->{tweakparams_user_login}, group => 'tweakparams' },
{ user => $config->{canconfirm_user_login}, group => 'canconfirm' },
{ user => $config->{editbugs_user_login}, group => 'editbugs' },
);
say 'adding users to groups...';
foreach my $user_group (@users_groups) {
my $group = Bugzilla::Group->new({ name => $user_group->{group} });
my $user = Bugzilla::User->new({ name => $user_group->{user} });
my $sth_add_mapping =
$dbh->prepare('INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
VALUES (?, ?, ?, ?)');
# Don't crash if the entry already exists.
eval { $sth_add_mapping->execute($user->id, $group->id, 0, GRANT_DIRECT); };
}
##########################################################################
# Associate Products with groups
##########################################################################
# Associate the QA-Selenium-TEST group with the QA-Selenium-TEST.
my $created_group = Bugzilla::Group->new({ name => 'QA-Selenium-TEST' });
my $secret_product = Bugzilla::Product->new({ name => 'QA-Selenium-TEST' });
my $no_entry = Bugzilla::Product->new({ name => 'QA Entry Only' });
my $no_search = Bugzilla::Product->new({ name => 'QA Search Only' });
say 'restricting products to groups...';
# Don't crash if the entries already exist.
my $sth = $dbh->prepare('INSERT INTO group_control_map
(group_id, product_id, entry, membercontrol, othercontrol, canedit)
VALUES (?, ?, ?, ?, ?, ?)');
eval { $sth->execute($created_group->id, $secret_product->id, 1, CONTROLMAPMANDATORY, CONTROLMAPMANDATORY, 0); };
eval { $sth->execute($created_group->id, $no_entry->id, 1, CONTROLMAPNA, CONTROLMAPNA, 0); };
eval { $sth->execute($created_group->id, $no_search->id, 0, CONTROLMAPMANDATORY, CONTROLMAPMANDATORY, 0); };
##########################################################################
# Create flag types
##########################################################################
my @flagtypes = (
{name => 'spec_multi_flag', desc => 'Specifically requestable and multiplicable bug flag',
is_requestable => 1, is_requesteeble => 1, is_multiplicable => 1, grant_group => 'editbugs',
target_type => 'b', cc_list => '', inclusions => ['Another Product:c1']},
);
say 'creating flag types...';
foreach my $flag (@flagtypes) {
# The name is not unique, even within a single product/component, so there is NO WAY
# to know if the existing flag type is the one we want or not.
# As our Selenium scripts would be confused anyway if there is already such a flag name,
# we simply skip it and assume the existing flag type is the one we want.
next if Bugzilla::FlagType->new({ name => $flag->{name} });
my $grant_group_id = $flag->{grant_group} ? Bugzilla::Group->new({ name => $flag->{grant_group} })->id : undef;
my $request_group_id = $flag->{request_group} ? Bugzilla::Group->new({ name => $flag->{request_group} })->id : undef;
$dbh->do('INSERT INTO flagtypes (name, description, cc_list, target_type, is_requestable,
is_requesteeble, is_multiplicable, grant_group_id, request_group_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
undef, ($flag->{name}, $flag->{desc}, $flag->{cc_list}, $flag->{target_type},
$flag->{is_requestable}, $flag->{is_requesteeble}, $flag->{is_multiplicable},
$grant_group_id, $request_group_id));
my $type_id = $dbh->bz_last_key('flagtypes', 'id');
foreach my $inclusion (@{$flag->{inclusions}}) {
my ($product, $component) = split(':', $inclusion);
my ($prod_id, $comp_id);
if ($product) {
my $prod_obj = Bugzilla::Product->new({ name => $product });
$prod_id = $prod_obj->id;
if ($component) {
$comp_id = Bugzilla::Component->new({ name => $component, product => $prod_obj})->id;
}
}
$dbh->do('INSERT INTO flaginclusions (type_id, product_id, component_id)
VALUES (?, ?, ?)',
undef, ($type_id, $prod_id, $comp_id));
}
}
##########################################################################
# Create custom fields
##########################################################################
my @fields = (
{ name => 'cf_QA_status',
description => 'QA Status',
type => FIELD_TYPE_MULTI_SELECT,
sortkey => 100,
mailhead => 0,
enter_bug => 1,
obsolete => 0,
custom => 1,
values => ['verified', 'in progress', 'untested']
},
{ name => 'cf_single_select',
description => 'SingSel',
type => FIELD_TYPE_SINGLE_SELECT,
mailhead => 0,
enter_bug => 1,
custom => 1,
values => [qw(one two three)],
},
);
say 'creating custom fields...';
foreach my $f (@fields) {
# Skip existing custom fields.
next if Bugzilla::Field->new({ name => $f->{name} });
my @values;
if (exists $f->{values}) {
@values = @{$f->{values}};
# We have to delete this key, else create() will complain
# that 'values' is not an existing column name.
delete $f->{values};
}
my $field = Bugzilla::Field->create($f);
# Now populate the table with valid values, if necessary.
next unless scalar @values;
my $sth = $dbh->prepare('INSERT INTO ' . $field->name . ' (value) VALUES (?)');
foreach my $value (@values) {
$sth->execute($value);
}
}
####################################################################
# Set Parameters That Require Other Things To Have Been Done First #
####################################################################
if (Bugzilla->params->{insidergroup} ne 'QA-Selenium-TEST') {
SetParam('insidergroup', 'QA-Selenium-TEST');
write_params();
}
if (Bugzilla->params->{timetrackinggroup} ne 'editbugs') {
SetParam('timetrackinggroup', 'editbugs');
write_params();
}
########################
# Create a Private Bug #
########################
my $test_user = Bugzilla::User->check($config->{QA_Selenium_TEST_user_login});
$test_user->{'groups'} = [
Bugzilla::Group->new({ name => 'editbugs' }),
Bugzilla::Group->new({ name => 'QA-Selenium-TEST' })
]; # editbugs is needed for alias creation
Bugzilla->set_user($test_user);
if (Bugzilla::Bug->new('private_bug')->{error}) {
say 'Creating private bug...';
my %priv_values = %field_values;
$priv_values{alias} = 'private_bug';
$priv_values{product} = 'QA-Selenium-TEST';
$priv_values{component} = 'QA-Selenium-TEST';
my $bug = Bugzilla::Bug->create(\%priv_values);
say 'Bug ' . $bug->id . ' (alias: private_bug) created';
}
######################
# Create Attachments #
######################
say 'creating attachments...';
# We use the contents of this script as the attachment.
open(my $attachment_fh, '<', __FILE__) or die __FILE__ . ": $!";
my $attachment_contents;
{
local $/;
$attachment_contents = <$attachment_fh>;
}
close($attachment_fh);
foreach my $alias (qw(public_bug private_bug)) {
my $bug = Bugzilla::Bug->new($alias);
foreach my $is_private (0, 1) {
Bugzilla::Attachment->create({
bug => $bug,
data => $attachment_contents,
description => "${alias}_${is_private}",
filename => "${alias}_${is_private}.pl",
mimetype => 'application/x-perl',
isprivate => $is_private,
});
}
}
###################
# Create Keywords #
###################
my @keywords = (
{ name => 'test-keyword-1',
description => 'Created for Bugzilla QA Tests, Keyword 1' },
{ name => 'test-keyword-2',
description => 'Created for Bugzilla QA Tests, Keyword 2' },
);
say 'creating keywords...';
foreach my $kw (@keywords) {
next if Bugzilla::Keyword->new({ name => $kw->{name} });
Bugzilla::Keyword->create($kw);
}
############################
# Install the QA extension #
############################
say 'copying the QA extension...';
my $output = `cp -R ../extensions/QA $conf_path/extensions/.`;
print $output if $output;
my $cwd = cwd();
chdir($conf_path);
$output = `perl contrib/fixperms.pl`;
print $output if $output;
chdir($cwd);
say 'installation and configuration complete!';
Index: Bugzilla/Config/MTA.pm
===================================================================
RCS file: /cvsroot/mozilla/webtools/bugzilla/Bugzilla/Config/MTA.pm,v
retrieving revision 1.13
diff -3 -p -u -r1.13 MTA.pm
--- Bugzilla/Config/MTA.pm 13 Nov 2006 23:32:28 -0000 1.13
+++ Bugzilla/Config/MTA.pm 9 Dec 2006 12:19:44 -0000
@@ -44,7 +44,9 @@ sub get_param_list {
{
name => 'mail_delivery_method',
type => 's',
- choices => [Email::Send->new()->all_mailers(), 'None'],
+ # Bugzilla is not ready yet to send mails to newsgroups, and 'IO'
+ # is of no use for now as we already have our own 'Test' mode.
+ choices => [grep {$_ ne 'NNTP' && $_ ne 'IO'} Email::Send->new()->all_mailers(), 'None'],
default => 'Sendmail',
checker => \&check_mail_delivery_method
},
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# To make this configuration file useful to you, you have to:
# - set the path and URL to your Bugzilla installation.
# - replace @my.company by something more relevant to you,
# also what comes before @my.company if you want/need to.
# - set passwords for each user accounts.
{ 'browser' => '*firefox',
'experimental_browser_launcher' => '*chrome',
'host' => 'localhost',
'port' => 4444,
'browser_url' => 'http://localhost',
'attachment_file' => '/home/bugzilla/devel/htdocs/bugzilla/xt/config/patch.diff',
'bugzilla_installation' => 'bugzilla',
'bugzilla_path' => '/home/bugzilla/devel/htdocs/bugzilla',
'admin_user_login' => 'admin@my.company',
'admin_user_passwd' => '******',
'admin_user_username' => 'admin',
'admin_user_api_key' => 'zQ5TSBzq7tTZMtKYq9K1ZqJMjifKx3cPL7pIGk9Q',
'permanent_user' => 'permanent_user@my.company',
'unprivileged_user_login' => 'no-privs@my.company',
'unprivileged_user_passwd' => '******',
'unprivileged_user_username' => 'no-privs',
'unprivileged_user_login_truncated' => 'no-privs@my',
'unprivileged_user_api_key' => 'zQ5TSBzqP4nrdBKYq9Re4qJrjifKx3cK07pIGk9Q',
'QA_Selenium_TEST_user_login' => 'QA-Selenium-TEST@my.company',
'QA_Selenium_TEST_user_passwd' => '******',
'editbugs_user_login' => 'editbugs@my.company',
'editbugs_user_passwd' => '******',
'editbugs_user_api_key' => 'zQ5ewBzq3gTrdBKYq9K1ZqJMjifKx3cKleE6k9TQ',
'canconfirm_user_login' => 'canconfirm@my.company',
'canconfirm_user_passwd' => '******',
'tweakparams_user_login' => 'tweakparams@my.company',
'tweakparams_user_login_truncated' => 'tweakparams@my',
'tweakparams_user_passwd' => '******',
'disabled_user_login' => 'disabled@my.company',
'disabled_user_passwd' => '******',
'common_email' => '@my.company',
'test_extensions' => 0,
};
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::Extension::QA;
use 5.10.1;
use strict;
use warnings;
use constant NAME => 'QA';
use constant REQUIRED_MODULES => [
];
use constant OPTIONAL_MODULES => [
];
__PACKAGE__->NAME;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::Extension::QA;
use 5.10.1;
use strict;
use warnings;
use base qw(Bugzilla::Extension);
use Bugzilla::Extension::QA::Util;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Bug;
use Bugzilla::User;
our $VERSION = '1.0';
sub page_before_template {
my ($self, $args) = @_;
return if $args->{page_id} ne 'qa/email_in.html';
my $template = Bugzilla->template;
my $cgi = Bugzilla->cgi;
print $cgi->header;
# Needed to make sure he can access and edit bugs.
my $user = Bugzilla::User->check($cgi->param('sender'));
Bugzilla->set_user($user);
my ($output, $tmpl_file);
my $action = $cgi->param('action') || '';
my $vars = { sender => $user, action => $action, pid => $$ };
if ($action eq 'create') {
$tmpl_file = 'qa/create_bug.txt.tmpl';
}
elsif ($action eq 'create_with_headers') {
$tmpl_file = 'qa/create_bug_with_headers.txt.tmpl';
}
elsif ($action =~ /^update(_with_headers)?$/) {
my $f = $1 || '';
$tmpl_file = "qa/update_bug$f.txt.tmpl";
my $bug = Bugzilla::Bug->check($cgi->param('bug_id'));
$vars->{bug_id} = $bug->id;
}
else {
ThrowUserError('unknown_action', { action => $action });
}
$template->process($tmpl_file, $vars, \$output)
or ThrowTemplateError($template->error());
my $file = "/tmp/email_in_$$.txt";
open(FH, '>', $file);
print FH $output;
close FH;
$output = `email_in.pl -v < $file 2>&1`;
unlink $file;
parse_output($output, $vars);
$template->process('qa/results.html.tmpl', $vars)
or ThrowTemplateError($template->error());
}
__PACKAGE__->NAME;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package Bugzilla::Extension::QA::Util;
use 5.10.1;
use strict;
use warnings;
use base qw(Exporter);
our @EXPORT = qw(
parse_output
);
sub parse_output {
my ($output, $vars) = @_;
$vars->{error} = ($output =~ /software error/i) ? 1 : 0;
$vars->{output} = $output;
$vars->{bug_id} ||= ($output =~ /Created bug (\d+)/i) ? $1 : undef;
}
1;
Template hooks go in this directory. Template hooks are called in normal
Bugzilla templates like [% Hook.process('some-hook') %].
More information about them can be found in the documentation of
Bugzilla::Extension. (Do "perldoc Bugzilla::Extension" from the main
Bugzilla directory to see that documentation.)
[%# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#%]
Normal templates go in this directory. You can load them in your
code like this:
use Bugzilla::Error;
my $template = Bugzilla->template;
$template->process('qa/some-template.html.tmpl')
or ThrowTemplateError($template->error());
That would be how to load a file called some-template.html.tmpl that
was in this directory.
Note that you have to be careful that the full path of your template
never conflicts with a template that exists in Bugzilla or in
another extension, or your template might override that template. That's why
we created this directory called 'qa' for you, so you
can put your templates in here to help avoid conflicts.
[%# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#%]
From: [% sender.email %]
Subject: [% terms.Bug %] created using email_in.pl
Content-Type: text/plain; charset="UTF-8"
@product = TestProduct
@component = TestComponent
@version = unspecified
This [% terms.bug %] has been created using email_in.pl (PID: [% pid %]).
[%# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#%]
From - Sat Jan 1 18:38:17 2011
X-Account-Key: account2
X-UIDL: GmailId12d42784d83cb4a4
X-Mozilla-Status: 0011
X-Mozilla-Status2: 00000000
X-Mozilla-Keys:
Return-Path: <foo@bar.com>
Received: from [192.168.0.2] (provider.com [51.162.153.14])
by mx.google.com with ESMTPS id m10sm12712256wbc.4.2011.01.01.09.38.01
(version=TLSv1/SSLv3 cipher=RC4-MD5);
Sat, 01 Jan 2011 09:38:01 -0800 (PST)
Message-ID: <4D1F6580.9060076@gmail.com>
Date: Sat, 01 Jan 2011 18:38:08 +0100
User-Agent: Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.2.13) Gecko/20101207 Lightning/1.0b2 Thunderbird/3.1.7
MIME-Version: 1.0
From: [% sender.email %]
Subject: [% terms.Bug %] created using email_in.pl (with email headers)
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: 8bit
@product = TestProduct
@component = TestComponent
@version = unspecified
This [% terms.bug %] has been created using email_in.pl (PID: [% pid %]) with email headers.
[%# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#%]
[% title = BLOCK %]
[% IF error %]
Unexpected error
[% ELSE %]
email_in.pl output
[% END %]
[% END %]
[% PROCESS global/header.html.tmpl %]
<h1>Action '[% action FILTER html %]' successful</h1>
<div>
<p>PID: <span id="pid">[% pid FILTER html %]</span></p>
<p>[%+ terms.Bug %] ID: <span id="bug_id">[% bug_id FILTER html %]</span></p>
<p>Full output:</p>
<pre id="output">[% output FILTER html_light %]</pre>
[% PROCESS global/footer.html.tmpl %]
[%# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#%]
From: [% sender.email %]
Subject: [[% terms.Bug %] [%+ bug_id %]] This subject is ignored, only the [% terms.bug %] ID matters
Content-Type: text/plain; charset="UTF-8"
Comment added by email_in.pl (PID: [% pid %]). No other changes.
[%# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#%]
From - Sat Jan 1 18:38:17 2011
X-Account-Key: account2
X-UIDL: GmailId12d42784d83cb4a4
X-Mozilla-Status: 0011
X-Mozilla-Status2: 00000000
X-Mozilla-Keys:
Return-Path: <foo@bar.com>
Received: from [192.168.0.2] (provider.com [51.162.153.14])
by mx.google.com with ESMTPS id m10sm12712256wbc.4.2011.01.01.09.38.01
(version=TLSv1/SSLv3 cipher=RC4-MD5);
Sat, 01 Jan 2011 09:38:01 -0800 (PST)
Message-ID: <4D1F6580.9060076@gmail.com>
Date: Sat, 01 Jan 2011 18:38:08 +0100
User-Agent: Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.2.13) Gecko/20101207 Lightning/1.0b2 Thunderbird/3.1.7
MIME-Version: 1.0
From: [% sender.email %]
Subject: [[% terms.Bug %] [%+ bug_id %]] This subject is ignored, only the [% terms.bug %] ID matters
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: 8bit
Comment added by email_in.pl (PID: [% pid %]) with email headers. No other changes.
Web-accessible files, like JavaScript, CSS, and images go in this
directory. You can reference them directly in your HTML. For example,
if you have a file called "style.css" and your extension is called
"Foo", you would put it in "extensions/Foo/web/style.css", and then
you could link to it in HTML like:
<link href="extensions/Foo/web/style.css" rel="stylesheet" type="text/css">
\ No newline at end of file
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# This module tests Bugzilla/Search.pm. It uses various constants
# that are in Bugzilla::Test::Search::Constants, in xt/lib/.
#
# It does this by:
# 1) Creating a bunch of field values. Each field value is
# randomly named and fully unique.
# 2) Creating a bunch of bugs that use those unique field
# values. Each bug has different characteristics--see
# the comment above the NUM_BUGS constant for a description
# of each bug.
# 3) Running searches using the combination of every search operator against
# every field. The tests that we run are described by the TESTS constant.
# Some of the operator/field combinations are known to be broken--
# these are listed in the KNOWN_BROKEN constant.
# 4) For each search, we make sure that certain bugs are contained in
# the search, and certain other bugs are not contained in the search.
# The code for the operator/field tests is mostly in
# Bugzilla::Test::Search::FieldTest.
# 5) After testing each operator/field combination's functionality, we
# do additional tests to make sure that there are no SQL injections
# possible via any operator/field combination. The code for the
# SQL Injection tests is in Bugzilla::Test::Search::InjectionTest.
#
# Generally, the only way that you should modify the behavior of this
# script is by modifying the constants.
package Bugzilla::Test::Search;
use strict;
use warnings;
use Bugzilla::Attachment;
use Bugzilla::Bug ();
use Bugzilla::Constants;
use Bugzilla::Field;
use Bugzilla::Field::Choice;
use Bugzilla::FlagType;
use Bugzilla::Group;
use Bugzilla::Install ();
use Bugzilla::Test::Search::Constants;
use Bugzilla::Test::Search::CustomTest;
use Bugzilla::Test::Search::FieldTestNormal;
use Bugzilla::Test::Search::OperatorTest;
use Bugzilla::User ();
use Bugzilla::Util qw(generate_random_password);
use Carp;
use DateTime;
use Scalar::Util qw(blessed);
###############
# Constructor #
###############
sub new {
my ($class, $options) = @_;
return bless { options => $options }, $class;
}
#############
# Accessors #
#############
sub options { return $_[0]->{options} }
sub option { return $_[0]->{options}->{$_[1]} }
sub num_tests {
my ($self) = @_;
my @top_operators = $self->top_level_operators;
my @all_operators = $self->all_operators;
my $top_operator_tests = $self->_total_operator_tests(\@top_operators);
my $all_operator_tests = $self->_total_operator_tests(\@all_operators);
my @fields = $self->all_fields;
# Basically, we run TESTS_PER_RUN tests for each field/operator combination.
my $top_combinations = $top_operator_tests * scalar(@fields);
my $all_combinations = $all_operator_tests * scalar(@fields);
# But we also have ORs, for which we run combinations^2 tests.
my $join_tests = $self->option('long')
? ($top_combinations * $all_combinations) : 0;
# And AND tests, which means we run 2x $join_tests;
$join_tests = $join_tests * 2;
# Also, because of NOT tests and Normal tests, we run 3x $top_combinations.
my $basic_tests = $top_combinations * 3;
my $operator_field_tests = ($basic_tests + $join_tests) * TESTS_PER_RUN;
# Then we test each field/operator combination for SQL injection.
my @injection_values = INJECTION_TESTS;
my $sql_injection_tests = scalar(@fields) * scalar(@top_operators)
* scalar(@injection_values) * NUM_SEARCH_TESTS;
# This @{ [] } thing is the only reasonable way to get a count out of a
# constant array.
my $special_tests = scalar(@{ [SPECIAL_PARAM_TESTS, CUSTOM_SEARCH_TESTS] })
* TESTS_PER_RUN;
return $operator_field_tests + $sql_injection_tests + $special_tests;
}
sub _total_operator_tests {
my ($self, $operators) = @_;
# Some operators have more than one test. Find those ones and add
# them to the total operator tests
my $extra_operator_tests;
foreach my $operator (@$operators) {
my $tests = TESTS->{$operator};
next if !$tests;
my $extra_num = scalar(@$tests) - 1;
$extra_operator_tests += $extra_num;
}
return scalar(@$operators) + $extra_operator_tests;
}
sub all_operators {
my ($self) = @_;
if (not $self->{all_operators}) {
my @operators;
if (my $limit_operators = $self->option('operators')) {
@operators = split(',', $limit_operators);
}
else {
@operators = sort (keys %{ Bugzilla::Search::OPERATORS() });
}
# "substr" is just a backwards-compatibility operator, same as "substring".
@operators = grep { $_ ne 'substr' } @operators;
$self->{all_operators} = \@operators;
}
return @{ $self->{all_operators} };
}
sub all_fields {
my $self = shift;
if (not $self->{all_fields}) {
$self->_create_custom_fields();
my @fields = @{ Bugzilla->fields };
@fields = sort { $a->name cmp $b->name } @fields;
$self->{all_fields} = \@fields;
}
return @{ $self->{all_fields} };
}
sub top_level_operators {
my ($self) = @_;
if (!$self->{top_level_operators}) {
my @operators;
my $limit_top = $self->option('top-operators');
if ($limit_top) {
@operators = split(',', $limit_top);
}
else {
@operators = $self->all_operators;
}
$self->{top_level_operators} = \@operators;
}
return @{ $self->{top_level_operators} };
}
sub text_fields {
my ($self) = @_;
my @text_fields = grep { $_->type == FIELD_TYPE_TEXTAREA
or $_->type == FIELD_TYPE_FREETEXT } $self->all_fields;
@text_fields = map { $_->name } @text_fields;
push(@text_fields, qw(short_desc status_whiteboard bug_file_loc see_also));
return @text_fields;
}
sub bugs {
my $self = shift;
$self->{bugs} ||= [map { $self->_create_one_bug($_) } (1..NUM_BUGS)];
return @{ $self->{bugs} };
}
# Get a numbered bug.
sub bug {
my ($self, $number) = @_;
return ($self->bugs)[$number - 1];
}
sub admin {
my $self = shift;
if (!$self->{admin_user}) {
my $admin = create_user("admin");
Bugzilla::Install::make_admin($admin);
$self->{admin_user} = $admin;
}
# We send back a fresh object every time, to make sure that group
# memberships are always up-to-date.
return new Bugzilla::User($self->{admin_user}->id);
}
sub nobody {
my $self = shift;
$self->{nobody} ||= Bugzilla::Group->create({ name => "nobody-" . random(),
description => "Nobody", isbuggroup => 1 });
return $self->{nobody};
}
sub everybody {
my ($self) = @_;
$self->{everybody} ||= create_group('To The Limit');
return $self->{everybody};
}
sub bug_create_value {
my ($self, $number, $field) = @_;
$field = $field->name if blessed($field);
if ($number == 6 and $field ne 'alias') {
$number = 1;
}
my $extra_values = $self->_extra_bug_create_values->{$number};
if (exists $extra_values->{$field}) {
return $extra_values->{$field};
}
return $self->_bug_create_values->{$number}->{$field};
}
sub bug_update_value {
my ($self, $number, $field) = @_;
$field = $field->name if blessed($field);
if ($number == 6 and $field ne 'alias') {
$number = 1;
}
return $self->_bug_update_values->{$number}->{$field};
}
# Values used to create the bugs.
sub _bug_create_values {
my $self = shift;
return $self->{bug_create_values} if $self->{bug_create_values};
my %values;
foreach my $number (1..NUM_BUGS) {
$values{$number} = $self->_create_field_values($number, 'for create');
}
$self->{bug_create_values} = \%values;
return $self->{bug_create_values};
}
# Values as they existed on the bug, at creation time. Used by the
# changedfrom tests.
sub _extra_bug_create_values {
my $self = shift;
$self->{extra_bug_create_values} ||= { map { $_ => {} } (1..NUM_BUGS) };
return $self->{extra_bug_create_values};
}
# Values used to update the bugs after they are created.
sub _bug_update_values {
my $self = shift;
return $self->{bug_update_values} if $self->{bug_update_values};
my %values;
foreach my $number (1..NUM_BUGS) {
$values{$number} = $self->_create_field_values($number);
}
$self->{bug_update_values} = \%values;
return $self->{bug_update_values};
}
##############################
# General Helper Subroutines #
##############################
sub random {
$_[0] ||= FIELD_SIZE;
generate_random_password(@_);
}
# We need to use a custom timestamp for each create() and update(),
# because the database returns the same value for LOCALTIMESTAMP(0)
# for the entire transaction, and we need each created bug to have
# its own creation_ts and delta_ts.
sub timestamp {
my ($day, $second) = @_;
return DateTime->new(
year => 2037,
month => 1,
day => $day,
hour => 12,
minute => $second,
second => 0,
# We make it floating because the timezone doesn't matter for our uses,
# and we want totally consistent behavior across all possible machines.
time_zone => 'floating',
);
}
sub create_keyword {
my ($number) = @_;
return Bugzilla::Keyword->create({
name => "$number-keyword-" . random(),
description => "Keyword $number" });
}
sub create_user {
my ($prefix) = @_;
my $user_name = $prefix . '-' . random(15) . "@" . random(12)
. "." . random(3);
my $user_realname = $prefix . '-' . random();
my $user = Bugzilla::User->create({
login_name => $user_name,
realname => $user_realname,
cryptpassword => '*',
});
return $user;
}
sub create_group {
my ($prefix) = @_;
return Bugzilla::Group->create({
name => "$prefix-group-" . random(), description => "Everybody $prefix",
userregexp => '.*', isbuggroup => 1 });
}
sub create_legal_value {
my ($field, $number) = @_;
my $type = Bugzilla::Field::Choice->type($field);
my $field_name = $field->name;
return $type->create({ value => "$number-$field_name-" . random(),
is_open => 0 });
}
#########################
# Custom Field Creation #
#########################
sub _create_custom_fields {
my ($self) = @_;
return if !$self->option('add-custom-fields');
while (my ($type, $name) = each %{ CUSTOM_FIELDS() }) {
my $exists = new Bugzilla::Field({ name => $name });
next if $exists;
Bugzilla::Field->create({
name => $name,
type => $type,
description => "Search Test Field $name",
enter_bug => 1,
custom => 1,
buglist => 1,
is_mandatory => 0,
});
}
}
########################
# Field Value Creation #
########################
sub _create_field_values {
my ($self, $number, $for_create) = @_;
my $dbh = Bugzilla->dbh;
Bugzilla->set_user($self->admin);
my @selects = grep { $_->is_select } $self->all_fields;
my %values;
foreach my $field (@selects) {
next if $field->is_abnormal;
$values{$field->name} = create_legal_value($field, $number)->name;
}
my $group = create_group($number);
$values{groups} = [$group->name];
$values{'keywords'} = create_keyword($number)->name;
foreach my $field (qw(assigned_to qa_contact reporter cc)) {
$values{$field} = create_user("$number-$field")->login;
}
my $classification = Bugzilla::Classification->create(
{ name => "$number-classification-" . random() });
$classification = $classification->name;
my $version = "$number-version-" . random();
my $milestone = "$number-tm-" . random(15);
my $product = Bugzilla::Product->create({
name => "$number-product-" . random(),
description => 'Created by t/search.t',
defaultmilestone => $milestone,
classification => $classification,
version => $version,
allows_unconfirmed => 1,
});
foreach my $item ($group, $self->nobody) {
$product->set_group_controls($item,
{ membercontrol => CONTROLMAPSHOWN,
othercontrol => CONTROLMAPNA });
}
# $product->update() is called lower down.
my $component = Bugzilla::Component->create({
product => $product, name => "$number-component-" . random(),
initialowner => create_user("$number-defaultowner")->login,
initialqacontact => create_user("$number-defaultqa")->login,
initial_cc => [create_user("$number-initcc")->login],
description => "Component $number" });
$values{'product'} = $product->name;
$values{'component'} = $component->name;
$values{'target_milestone'} = $milestone;
$values{'version'} = $version;
foreach my $field ($self->text_fields) {
# We don't add a - after $field for the text fields, because
# if we do, fulltext searching for short_desc pulls out
# "short_desc" as a word and matches it in every bug.
my $value = "$number-$field" . random();
if ($field eq 'bug_file_loc' or $field eq 'see_also') {
$value = "http://$value-" . random(3)
. "/show_bug.cgi?id=$number";
}
$values{$field} = $value;
}
$values{'tag'} = ["$number-tag-" . random()];
my @date_fields = grep { $_->type == FIELD_TYPE_DATETIME } $self->all_fields;
foreach my $field (@date_fields) {
# We use 03 as the month because that differs from our creation_ts,
# delta_ts, and deadline. (It's nice to have recognizable values
# for each field when debugging.)
my $second = $for_create ? $number : $number + 1;
$values{$field->name} = "2037-03-0$number 12:34:0$second";
}
$values{alias} = "$number-alias-" . random(12);
# Prefixing the original comment with "description" makes the
# lesserthan and greaterthan tests behave predictably.
my $comm_prefix = $for_create ? "description-" : '';
$values{comment} = "$comm_prefix$number-comment-" . random()
. ' ' . random();
my @flags;
my $setter = create_user("$number-setters.login_name");
my $requestee = create_user("$number-requestees.login_name");
$values{set_flags} = _create_flags($number, $setter, $requestee);
my $month = $for_create ? "12" : "02";
$values{'deadline'} = "2037-$month-0$number";
my $estimate_times = $for_create ? 10 : 1;
$values{estimated_time} = $estimate_times * $number;
$values{attachment} = _get_attach_values($number, $for_create);
# Some things only happen on the first bug.
if ($number == 1) {
# We use 6 as the prefix for the extra values, because bug 6's values
# don't otherwise get used (since bug 6 is created as a clone of
# bug 1). This also makes sure that our greaterthan/lessthan
# tests work properly.
my $extra_group = create_group(6);
$product->set_group_controls($extra_group,
{ membercontrol => CONTROLMAPSHOWN,
othercontrol => CONTROLMAPNA });
$values{groups} = [$values{groups}->[0], $extra_group->name];
my $extra_keyword = create_keyword(6);
$values{keywords} = [$values{keywords}, $extra_keyword->name];
my $extra_cc = create_user("6-cc");
$values{cc} = [$values{cc}, $extra_cc->login];
my @multi_selects = grep { $_->type == FIELD_TYPE_MULTI_SELECT }
$self->all_fields;
foreach my $field (@multi_selects) {
my $new_value = create_legal_value($field, 6);
my $name = $field->name;
$values{$name} = [$values{$name}, $new_value->name];
}
push(@{ $values{'tag'} }, "6-tag-" . random());
}
# On bug 5, any field that *can* be left empty, *is* left empty.
if ($number == 5) {
my @set_fields = grep { $_->type == FIELD_TYPE_SINGLE_SELECT }
$self->all_fields;
@set_fields = map { $_->name } @set_fields;
push(@set_fields, qw(short_desc version reporter));
foreach my $key (keys %values) {
delete $values{$key} unless grep { $_ eq $key } @set_fields;
}
}
$product->update();
return \%values;
}
# Flags
sub _create_flags {
my ($number, $setter, $requestee) = @_;
my $flagtypes = _create_flagtypes($number);
my %flags;
foreach my $type (qw(a b)) {
$flags{$type} = _get_flag_values(@_, $flagtypes->{$type});
}
return \%flags;
}
sub _create_flagtypes {
my ($number) = @_;
my $dbh = Bugzilla->dbh;
my $name = "$number-flag-" . random();
my $desc = "FlagType $number";
my %flagtypes;
foreach my $target (qw(a b)) {
$dbh->do("INSERT INTO flagtypes
(name, description, target_type, is_requestable,
is_requesteeble, is_multiplicable, cc_list)
VALUES (?,?,?,1,1,1,'')",
undef, $name, $desc, $target);
my $id = $dbh->bz_last_key('flagtypes', 'id');
$dbh->do('INSERT INTO flaginclusions (type_id) VALUES (?)',
undef, $id);
my $flagtype = new Bugzilla::FlagType($id);
$flagtypes{$target} = $flagtype;
}
return \%flagtypes;
}
sub _get_flag_values {
my ($number, $setter, $requestee, $flagtype) = @_;
my @set_flags;
if ($number <= 2) {
foreach my $value (qw(? - + ?)) {
my $flag = { type_id => $flagtype->id, status => $value,
setter => $setter, flagtype => $flagtype };
push(@set_flags, $flag);
}
$set_flags[0]->{requestee} = $requestee->login;
}
else {
@set_flags = ({ type_id => $flagtype->id, status => '+',
setter => $setter, flagtype => $flagtype });
}
return \@set_flags;
}
# Attachments
sub _get_attach_values {
my ($number, $for_create) = @_;
my $boolean = $number == 1 ? 1 : 0;
if ($for_create) {
$boolean = !$boolean ? 1 : 0;
}
my $ispatch = $for_create ? 'ispatch' : 'is_patch';
my $isobsolete = $for_create ? 'isobsolete' : 'is_obsolete';
my $isprivate = $for_create ? 'isprivate' : 'is_private';
my $mimetype = $for_create ? 'mimetype' : 'content_type';
my %values = (
description => "$number-attach_desc-" . random(),
filename => "$number-filename-" . random(),
$ispatch => $boolean,
$isobsolete => $boolean,
$isprivate => $boolean,
$mimetype => "text/x-$number-" . random(),
);
if ($for_create) {
$values{data} = "$number-data-" . random() . random();
}
return \%values;
}
################
# Bug Creation #
################
sub _create_one_bug {
my ($self, $number) = @_;
my $dbh = Bugzilla->dbh;
# We need bug 6 to have a unique alias that is not a clone of bug 1's,
# so we get the alias separately from the other parameters.
my $alias = $self->bug_create_value($number, 'alias');
my $update_alias = $self->bug_update_value($number, 'alias');
# Otherwise, make bug 6 a clone of bug 1.
my $real_number = $number;
$number = 1 if $number == 6;
my $reporter = $self->bug_create_value($number, 'reporter');
Bugzilla->set_user(Bugzilla::User->check($reporter));
# We create the bug with one set of values, and then we change it
# to have different values.
my %params = %{ $self->_bug_create_values->{$number} };
$params{alias} = $alias;
# There are some things in bug_create_values that shouldn't go into
# create().
delete @params{qw(attachment set_flags tag)};
my ($status, $resolution, $see_also) =
delete @params{qw(bug_status resolution see_also)};
# All the bugs are created with everconfirmed = 0.
$params{bug_status} = 'UNCONFIRMED';
my $bug = Bugzilla::Bug->create(\%params);
# These are necessary for the changedfrom tests.
my $extra_values = $self->_extra_bug_create_values->{$number};
foreach my $field (qw(comments remaining_time percentage_complete
keyword_objects everconfirmed dependson blocked
groups_in classification actual_time))
{
$extra_values->{$field} = $bug->$field;
}
$extra_values->{reporter_accessible} = $number == 1 ? 0 : 1;
$extra_values->{cclist_accessible} = $number == 1 ? 0 : 1;
if ($number == 5) {
# Bypass Bugzilla::Bug--we don't want any changes in bugs_activity
# for bug 5.
$dbh->do('UPDATE bugs SET qa_contact = NULL, reporter_accessible = 0,
cclist_accessible = 0 WHERE bug_id = ?',
undef, $bug->id);
$dbh->do('DELETE FROM cc WHERE bug_id = ?', undef, $bug->id);
my $ts = '1970-01-01 00:00:00';
$dbh->do('UPDATE bugs SET creation_ts = ?, delta_ts = ?
WHERE bug_id = ?', undef, $ts, $ts, $bug->id);
$dbh->do('UPDATE longdescs SET bug_when = ? WHERE bug_id = ?',
undef, $ts, $bug->id);
$bug->{creation_ts} = $ts;
$extra_values->{see_also} = [];
}
else {
# Manually set the creation_ts so that each bug has a different one.
#
# Also, manually update the resolution and bug_status, because
# we want to see both of them change in bugs_activity, so we
# have to start with values for both (and as of the time when I'm
# writing this test, Bug->create doesn't support setting resolution).
#
# Same for see_also.
my $timestamp = timestamp($number, $number - 1);
my $creation_ts = $timestamp->ymd . ' ' . $timestamp->hms;
$bug->{creation_ts} = $creation_ts;
$dbh->do('UPDATE longdescs SET bug_when = ? WHERE bug_id = ?',
undef, $creation_ts, $bug->id);
$dbh->do('UPDATE bugs SET creation_ts = ?, bug_status = ?,
resolution = ? WHERE bug_id = ?',
undef, $creation_ts, $status, $resolution, $bug->id);
$dbh->do('INSERT INTO bug_see_also (bug_id, value, class) VALUES (?,?,?)',
undef, $bug->id, $see_also, 'Bugzilla::BugUrl::Bugzilla');
$extra_values->{see_also} = $bug->see_also;
# All the tags must be created as the admin user, so that the
# admin user can find them, later.
my $original_user = Bugzilla->user;
Bugzilla->set_user($self->admin);
my $tags = $self->bug_create_value($number, 'tag');
$bug->add_tag($_) foreach @$tags;
$extra_values->{tags} = $tags;
Bugzilla->set_user($original_user);
if ($number == 1) {
# Bug 1 needs to start off with reporter_accessible and
# cclist_accessible being 0, so that when we change them to 1,
# that change shows up in bugs_activity.
$dbh->do('UPDATE bugs SET reporter_accessible = 0,
cclist_accessible = 0 WHERE bug_id = ?',
undef, $bug->id);
# Bug 1 gets three comments, so that longdescs.count matches it
# uniquely. The third comment is added in the middle, so that the
# last comment contains all of the important data, like work_time.
$bug->add_comment("1-comment-" . random(100));
}
my %update_params = %{ $self->_bug_update_values->{$number} };
my %reverse_map = reverse %{ Bugzilla::Bug->FIELD_MAP };
foreach my $db_name (keys %reverse_map) {
next if $db_name eq 'comment';
next if $db_name eq 'status_whiteboard';
if (exists $update_params{$db_name}) {
my $update_name = $reverse_map{$db_name};
$update_params{$update_name} = delete $update_params{$db_name};
}
}
my ($new_status, $new_res) =
delete @update_params{qw(status resolution)};
# Bypass the status workflow.
$bug->{bug_status} = $new_status;
$bug->{resolution} = $new_res;
$bug->{everconfirmed} = 1 if $number == 1;
# add/remove/set fields.
$update_params{keywords} = { set => $update_params{keywords} };
$update_params{groups} = { add => $update_params{groups},
remove => $bug->groups_in };
my @cc_remove = map { $_->login } @{ $bug->cc_users };
my $cc_new = $update_params{cc};
my @cc_add = ref($cc_new) ? @$cc_new : ($cc_new);
# We make the admin an explicit CC on bug 1 (but not on bug 6), so
# that we can test the %user% pronoun properly.
if ($real_number == 1) {
push(@cc_add, $self->admin->login);
}
$update_params{cc} = { add => \@cc_add, remove => \@cc_remove };
my $see_also_remove = $bug->see_also;
my $see_also_add = [$update_params{see_also}];
$update_params{see_also} = { add => $see_also_add,
remove => $see_also_remove };
$update_params{comment} = { body => $update_params{comment} };
$update_params{work_time} = $number;
# Setting work_time kills the remaining_time, so we need to
# preserve that. We add 8 because that produces an integer
# percentage_complete for bug 1, which is necessary for
# accurate "equals"-type searching.
$update_params{remaining_time} = $number + 8;
$update_params{reporter_accessible} = $number == 1 ? 1 : 0;
$update_params{cclist_accessible} = $number == 1 ? 1 : 0;
$update_params{alias} = $update_alias;
$bug->set_all(\%update_params);
my $flags = $self->bug_create_value($number, 'set_flags')->{b};
$bug->set_flags([], $flags);
$timestamp->set(second => $number);
$bug->update($timestamp->ymd . ' ' . $timestamp->hms);
$extra_values->{flags} = $bug->flags;
# It's not generally safe to do update() multiple times on
# the same Bug object.
$bug = new Bugzilla::Bug($bug->id);
my $update_flags = $self->bug_update_value($number, 'set_flags')->{b};
$_->{status} = 'X' foreach @{ $bug->flags };
$bug->set_flags($bug->flags, $update_flags);
if ($number == 1) {
my $comment_id = $bug->comments->[-1]->id;
$bug->set_comment_is_private({ $comment_id => 1 });
}
$bug->update($bug->delta_ts);
my $attach_create = $self->bug_create_value($number, 'attachment');
my $attachment = Bugzilla::Attachment->create({
bug => $bug,
creation_ts => $creation_ts,
%$attach_create });
# Store for the changedfrom tests.
$extra_values->{attachments} =
[new Bugzilla::Attachment($attachment->id)];
my $attach_update = $self->bug_update_value($number, 'attachment');
$attachment->set_all($attach_update);
# In order to keep the mimetype on the ispatch attachment,
# we need to bypass the validator.
$attachment->{mimetype} = $attach_update->{content_type};
my $attach_flags = $self->bug_update_value($number, 'set_flags')->{a};
$attachment->set_flags([], $attach_flags);
$attachment->update($bug->delta_ts);
}
# Values for changedfrom.
$extra_values->{creation_ts} = $bug->creation_ts;
$extra_values->{delta_ts} = $bug->creation_ts;
return new Bugzilla::Bug($bug->id);
}
###################################
# Test::Builder Memory Efficiency #
###################################
# Test::Builder stores information for each test run, but Test::Harness
# and TAP::Harness don't actually need this information. When we run 60
# million tests, the history eats up all our memory. (After about
# 1 million tests, memory usage is around 1 GB.)
#
# The only part of the history that Test::More actually *uses* is the "ok"
# field, which we store more efficiently, in an array, and then we re-populate
# the Test_Results in Test::Builder at the end of the test.
sub clean_test_history {
my ($self) = @_;
return if !$self->option('long');
my $builder = Test::More->builder;
my $current_test = $builder->current_test;
# I don't use details() because I don't want to copy the array.
my $results = $builder->{Test_Results};
my $check_test = $current_test - 1;
while (my $result = $results->[$check_test]) {
last if !$result;
$self->test_success($check_test, $result->{ok});
$check_test--;
}
# Truncate the test history array, but retain the current test number.
$builder->{Test_Results} = [];
$builder->{Curr_Test} = $current_test;
}
sub test_success {
my ($self, $index, $status) = @_;
$self->{test_success}->[$index] = $status;
return $self->{test_success};
}
sub repopulate_test_results {
my ($self) = @_;
return if !$self->option('long');
$self->clean_test_history();
# We create only two hashes, for memory efficiency.
my %ok = ( ok => 1 );
my %not_ok = ( ok => 0 );
my @results;
foreach my $success (@{ $self->{test_success} }) {
push(@results, $success ? \%ok : \%not_ok);
}
my $builder = Test::More->builder;
$builder->{Test_Results} = \@results;
}
##########
# Caches #
##########
# When doing AND and OR tests, we essentially test the same field/operator
# combinations over and over. So, if we're going to be running those tests,
# we cache the translated_value of the FieldTests globally so that we don't
# have to re-run the value-translation code every time (which can be pretty
# slow).
sub value_translation_cache {
my ($self, $field_test, $value) = @_;
return if !$self->option('long');
my $test_name = $field_test->name;
if (@_ == 3) {
$self->{value_translation_cache}->{$test_name} = $value;
}
return $self->{value_translation_cache}->{$test_name};
}
# When doing AND/OR tests, the value for transformed_value_was_equal
# (see Bugzilla::Test::Search::FieldTest) won't be recalculated
# if we pull our values from the value_translation_cache. So we need
# to also cache the values for transformed_value_was_equal.
sub was_equal_cache {
my ($self, $field_test, $number, $value) = @_;
return if !$self->option('long');
my $test_name = $field_test->name;
if (@_ == 4) {
$self->{tvwe_cache}->{$test_name}->{$number} = $value;
}
return $self->{tvwe_cache}->{$test_name}->{$number};
}
#############
# Main Test #
#############
sub run {
my ($self) = @_;
my $dbh = Bugzilla->dbh;
# We want backtraces on any "die" message or any warning.
# Otherwise it's hard to trace errors inside of Bugzilla::Search from
# reading automated test run results.
local $SIG{__WARN__} = \&Carp::cluck;
local $SIG{__DIE__} = \&Carp::confess;
$dbh->bz_start_transaction();
# Some parameters need to be set in order for the tests to function
# properly.
my $everybody = $self->everybody;
my $params = Bugzilla->params;
local $params->{'useclassification'} = 1;
local $params->{'useqacontact'} = 1;
local $params->{'usetargetmilestone'} = 1;
local $params->{'mail_delivery_method'} = 'None';
local $params->{'timetrackinggroup'} = $everybody->name;
local $params->{'insidergroup'} = $everybody->name;
$self->_setup_bugs();
# Even though _setup_bugs set us as an admin, we want to be sure at
# this point that we have an admin with refreshed group memberships.
Bugzilla->set_user($self->admin);
foreach my $test (CUSTOM_SEARCH_TESTS) {
my $custom_test = new Bugzilla::Test::Search::CustomTest($test, $self);
$custom_test->run();
}
foreach my $test (SPECIAL_PARAM_TESTS) {
my $operator_test =
new Bugzilla::Test::Search::OperatorTest($test->{operator}, $self);
my $field = Bugzilla::Field->check($test->{field});
my $special_test = new Bugzilla::Test::Search::FieldTestNormal(
$operator_test, $field, $test);
$special_test->run();
}
foreach my $operator ($self->top_level_operators) {
my $operator_test =
new Bugzilla::Test::Search::OperatorTest($operator, $self);
$operator_test->run();
}
# Rollbacks won't get rid of bugs_fulltext entries, so we do that ourselves.
my @bug_ids = map { $_->id } $self->bugs;
my $bug_id_string = join(',', @bug_ids);
$dbh->do("DELETE FROM bugs_fulltext WHERE bug_id IN ($bug_id_string)");
$dbh->bz_rollback_transaction();
$self->repopulate_test_results();
}
# This makes a few changes to the bugs after they're created--changes
# that can only be done after all the bugs have been created.
sub _setup_bugs {
my ($self) = @_;
$self->_setup_dependencies();
$self->_set_bug_id_fields();
$self->_protect_bug_6();
}
sub _setup_dependencies {
my ($self) = @_;
my $dbh = Bugzilla->dbh;
# Set up depedency relationships between the bugs.
# Bug 1 + 6 depend on bug 2 and block bug 3.
my $bug2 = $self->bug(2);
my $bug3 = $self->bug(3);
foreach my $number (1,6) {
my $bug = $self->bug($number);
my @original_delta = ($bug2->delta_ts, $bug3->delta_ts);
Bugzilla->set_user($bug->reporter);
$bug->set_dependencies([$bug2->id], [$bug3->id]);
$bug->update($bug->delta_ts);
# Setting dependencies changed the delta_ts on bug2 and bug3, so
# re-set them back to what they were before. However, we leave
# the correct update times in bugs_activity, so that the changed*
# searches still work right.
my $set_delta = $dbh->prepare(
'UPDATE bugs SET delta_ts = ? WHERE bug_id = ?');
foreach my $row ([$original_delta[0], $bug2->id],
[$original_delta[1], $bug3->id])
{
$set_delta->execute(@$row);
}
}
}
sub _set_bug_id_fields {
my ($self) = @_;
# BUG_ID fields couldn't be set before, because before we create bug 1,
# we don't necessarily have any valid bug ids.)
my @bug_id_fields = grep { $_->type == FIELD_TYPE_BUG_ID }
$self->all_fields;
foreach my $number (1..NUM_BUGS) {
my $bug = $self->bug($number);
$number = 1 if $number == 6;
next if $number == 5;
my $other_bug = $self->bug($number + 1);
Bugzilla->set_user($bug->reporter);
foreach my $field (@bug_id_fields) {
$bug->set_custom_field($field, $other_bug->id);
$bug->update($bug->delta_ts);
}
}
}
sub _protect_bug_6 {
my ($self) = @_;
my $dbh = Bugzilla->dbh;
Bugzilla->set_user($self->admin);
# Put bug6 in the nobody group.
my $nobody = $self->nobody;
# We pull it newly from the DB to be sure it's safe to call update()
# on.
my $bug6 = new Bugzilla::Bug($self->bug(6)->id);
$bug6->add_group($nobody);
$bug6->update($bug6->delta_ts);
# Remove the admin (and everybody else) from the $nobody group.
$dbh->do('DELETE FROM group_group_map
WHERE grantor_id = ? OR member_id = ?', undef,
$nobody->id, $nobody->id);
}
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# This test combines two field/operator combinations using AND in
# a single boolean chart.
package Bugzilla::Test::Search::AndTest;
use parent qw(Bugzilla::Test::Search::OrTest);
use Bugzilla::Test::Search::Constants;
use List::MoreUtils qw(all);
use constant type => 'AND';
#############
# Accessors #
#############
# In an AND test, bugs ARE supposed to be contained only if they are contained
# by ALL tests.
sub bug_is_contained {
my ($self, $number) = @_;
return all { $_->bug_is_contained($number) } $self->field_tests;
}
sub _bug_will_actually_be_contained {
my ($self, $number) = @_;
return all { $_->will_actually_contain_bug($number) } $self->field_tests;
}
##############################
# Bugzilla::Search arguments #
##############################
sub search_params {
my ($self) = @_;
my @all_params = map { $_->search_params } $self->field_tests;
my %params;
my $chart = 0;
foreach my $item (@all_params) {
$params{"field0-$chart-0"} = $item->{'field0-0-0'};
$params{"type0-$chart-0"} = $item->{'type0-0-0'};
$params{"value0-$chart-0"} = $item->{'value0-0-0'};
$chart++;
}
return \%params;
}
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# These are constants used by Bugzilla::Test::Search.
# See the comment at the top of that package for a general overview
# of how the search test works, and how the constants are used.
# More detailed information on each constant is available in the comments
# in this file.
package Bugzilla::Test::Search::Constants;
use parent qw(Exporter);
use Bugzilla::Constants;
use Bugzilla::Util qw(generate_random_password);
our @EXPORT = qw(
ATTACHMENT_FIELDS
BROKEN_NOT
COLUMN_TRANSLATION
COMMENT_FIELDS
CUSTOM_FIELDS
CUSTOM_SEARCH_TESTS
FIELD_SIZE
FIELD_SUBSTR_SIZE
FLAG_FIELDS
INJECTION_BROKEN_FIELD
INJECTION_BROKEN_OPERATOR
INJECTION_TESTS
KNOWN_BROKEN
NUM_BUGS
NUM_SEARCH_TESTS
SKIP_FIELDS
SPECIAL_PARAM_TESTS
SUBSTR_NO_FIELD_ADD
SUBSTR_SIZE
TESTS
TESTS_PER_RUN
USER_FIELDS
);
# Bug 1 is designed to be found by all the "equals" tests. It has
# multiple values for several fields where other fields only have
# one value.
#
# Bug 2 and 3 have a dependency relationship with Bug 1,
# but show up in "not equals" tests. We do use bug 2 in multiple-value
# tests.
#
# Bug 4 should never show up in any equals test, and has no relationship
# with any other bug. However, it does have all its fields set.
#
# Bug 5 only has values set for mandatory fields, to expose problems
# that happen with "not equals" tests failing to catch bugs that don't
# have a value set at all.
#
# Bug 6 is a clone of Bug 1, but is in a group that the searcher isn't
# in.
use constant NUM_BUGS => 6;
# How many tests there are for each operator/field combination other
# than the "contains" tests.
use constant NUM_SEARCH_TESTS => 3;
# This is how many tests get run for each field/operator.
use constant TESTS_PER_RUN => NUM_SEARCH_TESTS + NUM_BUGS;
# This is how many random characters we generate for most fields' names.
# (Some fields can't be this long, though, so they have custom lengths
# in Bugzilla::Test::Search).
use constant FIELD_SIZE => 30;
# These are the custom fields that are created if the BZ_MODIFY_DATABASE_TESTS
# environment variable is set.
use constant CUSTOM_FIELDS => {
FIELD_TYPE_FREETEXT, 'cf_freetext',
FIELD_TYPE_SINGLE_SELECT, 'cf_single_select',
FIELD_TYPE_MULTI_SELECT, 'cf_multi_select',
FIELD_TYPE_TEXTAREA, 'cf_textarea',
FIELD_TYPE_DATETIME, 'cf_datetime',
FIELD_TYPE_BUG_ID, 'cf_bugid',
};
# This translates fielddefs names into Search column names.
use constant COLUMN_TRANSLATION => {
creation_ts => 'opendate',
delta_ts => 'changeddate',
work_time => 'actual_time',
};
# Make comment field names to their Bugzilla::Comment accessor.
use constant COMMENT_FIELDS => {
longdesc => 'body',
commenter => 'author',
'longdescs.isprivate' => 'is_private',
};
# Same as above, for Bugzilla::Attachment.
use constant ATTACHMENT_FIELDS => {
mimetype => 'contenttype',
submitter => 'attacher',
thedata => 'data',
};
# Same, for Bugzilla::Flag.
use constant FLAG_FIELDS => {
'flagtypes.name' => 'name',
'setters.login_name' => 'setter',
'requestees.login_name' => 'requestee',
};
# These are fields that we don't test. Test::More will mark these
# "TODO & SKIP", and not run tests for them at all.
#
# We don't support days_elapsed or owner_idle_time yet.
use constant SKIP_FIELDS => qw(
owner_idle_time
days_elapsed
);
# All the fields that represent users.
use constant USER_FIELDS => qw(
assigned_to
cc
reporter
qa_contact
commenter
attachments.submitter
setters.login_name
requestees.login_name
);
# For the "substr"-type searches, how short of a substring should
# we use? The goal is to be shorter than the full string, but
# long enough to still be globally unique.
use constant SUBSTR_SIZE => 20;
# However, for some fields, we use a different size.
use constant FIELD_SUBSTR_SIZE => {
alias => 11,
# Just the month and day.
deadline => -5,
creation_ts => -8,
delta_ts => -8,
percentage_complete => 1,
work_time => 3,
remaining_time => 3,
target_milestone => 15,
longdesc => 25,
# Just the hour and minute.
FIELD_TYPE_DATETIME, -5,
};
# For most fields, we add the length of the name of the field plus
# the SUBSTR_SIZE specified above to determine how large of a substring
# we're going to use. However, for some fields, it doesn't make sense to
# add in their field name this way.
use constant SUBSTR_NO_FIELD_ADD => FIELD_TYPE_DATETIME, qw(
target_milestone remaining_time percentage_complete work_time
attachments.mimetype attachments.submitter attachments.filename
attachments.description flagtypes.name
);
################
# Known Broken #
################
# See the KNOWN_BROKEN constant for a general description of these
# "_BROKEN" constants.
# Shared between greaterthan and greaterthaneq.
#
# As with other fields, longdescs greaterthan matches if any comment
# matches (which might be OK).
#
# Same for keywords, and cc. Logically, all of these might
# be OK, but it makes the operation not the logical reverse of
# lessthaneq. What we're really saying here by marking these broken
# is that there ought to be some way of searching "all ccs" vs "any cc"
# (and same for the other fields).
use constant GREATERTHAN_BROKEN => (
cc => { contains => [1] },
);
# allwords and allwordssubstr have these broken tests in common.
use constant ALLWORDS_BROKEN => (
# allwordssubstr on cc fields matches against a single cc,
# instead of matching against all ccs on a bug.
cc => { contains => [1] },
# bug 828344 changed how these searches operate to revert back to the 4.0
# behavour, so these tests need to be updated (bug 849117).
'flagtypes.name' => { contains => [1] },
longdesc => { contains => [1] },
);
# Fields that don't generally work at all with changed* searches, but
# probably should.
use constant CHANGED_BROKEN => (
classification => { contains => [1] },
commenter => { contains => [1] },
percentage_complete => { contains => [1] },
'requestees.login_name' => { contains => [1] },
'setters.login_name' => { contains => [1] },
delta_ts => { contains => [1] },
);
# These are additional broken tests that changedfrom and changedto
# have in common.
use constant CHANGED_VALUE_BROKEN => (
bug_group => { contains => [1] },
cc => { contains => [1] },
estimated_time => { contains => [1] },
'flagtypes.name' => { contains => [1] },
keywords => { contains => [1] },
'longdescs.count' => { search => 1 },
FIELD_TYPE_MULTI_SELECT, { contains => [1] },
);
# Any test listed in KNOWN_BROKEN gets marked TODO by Test::More
# (using some complex code in Bugzilla::Test::Seach::FieldTest).
# This means that if you run the test under "prove -v", these tests will
# still show up as "not ok", but the test suite results won't show them
# as a failure.
#
# This constant contains operators as keys, which point to hashes. The hashes
# have field names as keys. Each field name points to a hash describing
# how that field/operator combination is broken. The "contains"
# array specifies that that particular "contains" test is expected
# to fail. If "search" is set to 1, then we expect the creation of the
# Bugzilla::Search object to fail.
#
# To allow handling custom fields, you can also use the field type as a key
# instead of the field name. Specifying explicit field names always overrides
# specifying a field type.
#
# Sometimes the operators have multiple tests, and one of them works
# while the other fails. In this case, we have a special override for
# "operator-value", which uniquely identifies tests.
use constant KNOWN_BROKEN => {
greaterthan => { GREATERTHAN_BROKEN },
greaterthaneq => { GREATERTHAN_BROKEN },
'allwordssubstr-<1>' => { ALLWORDS_BROKEN },
'allwords-<1>' => {
ALLWORDS_BROKEN,
},
'anywords-<1>' => {
'flagtypes.name' => { contains => [1,2,3,4,5] },
},
'anywords-<1> <2>' => {
'flagtypes.name' => { contains => [3,4,5] },
},
'anywordssubstr-<1> <2>' => {
'flagtypes.name' => { contains => [3,4,5] },
},
# setters.login_name and requestees.login name aren't tracked individually
# in bugs_activity, so can't be searched using this method.
#
# percentage_complete isn't tracked in bugs_activity (and it would be
# really hard to track). However, it adds a 0=0 term instead of using
# the changed* charts or simply denying them.
#
# delta_ts changedbefore/after should probably search for bugs based
# on their delta_ts.
#
# creation_ts changedbefore/after should search for bug creation dates.
#
# The commenter field changedbefore/after should search for comment
# creation dates.
#
# classification isn't being tracked properly in bugs_activity, I think.
#
# attach_data.thedata should search when attachments were created and
# who they were created by.
'changedbefore' => {
CHANGED_BROKEN,
'attach_data.thedata' => { contains => [1] },
},
'changedafter' => {
'attach_data.thedata' => { contains => [2,3,4] },
classification => { contains => [2,3,4] },
commenter => { contains => [2,3,4] },
delta_ts => { contains => [2,3,4] },
percentage_complete => { contains => [2,3,4] },
'requestees.login_name' => { contains => [2,3,4] },
'setters.login_name' => { contains => [2,3,4] },
},
changedfrom => {
CHANGED_BROKEN,
CHANGED_VALUE_BROKEN,
# All fields should have a way to search for "changing
# from a blank value" probably.
blocked => { contains => [3,4,5], no_criteria => 1 },
dependson => { contains => [2,4,5], no_criteria => 1 },
work_time => { contains => [1] },
FIELD_TYPE_BUG_ID, { contains => [5], no_criteria => 1 },
},
# changeto doesn't find remaining_time changes (possibly due to us not
# tracking that data properly).
#
# multi-valued fields are stored as comma-separated strings, so you
# can't do changedfrom/to on them.
#
# Perhaps commenter can either tell you who the last commenter was,
# or if somebody commented at a given time (combined with other
# charts).
#
# longdesc changedto/from doesn't do anything; maybe it should.
# Same for attach_data.thedata.
changedto => {
CHANGED_BROKEN,
CHANGED_VALUE_BROKEN,
'attach_data.thedata' => { contains => [1] },
longdesc => { contains => [1] },
remaining_time => { contains => [1] },
},
changedby => {
CHANGED_BROKEN,
# This should probably search the attacher or anybody who changed
# anything about an attachment at all.
'attach_data.thedata' => { contains => [1] },
# This should probably search the reporter.
creation_ts => { contains => [1] },
},
notequals => {
'flagtypes.name' => { contains => [1, 5] },
longdesc => { contains => [1] },
},
notregexp => {
'flagtypes.name' => { contains => [1, 5] },
longdesc => { contains => [1] },
},
notsubstring => {
'flagtypes.name' => { contains => [5] },
longdesc => { contains => [1] },
},
nowords => {
'flagtypes.name' => { contains => [1, 5] },
},
nowordssubstr => {
'flagtypes.name' => { contains => [5] },
},
};
###################
# Broken NotTests #
###################
# Common BROKEN_NOT values for the changed* fields.
use constant CHANGED_BROKEN_NOT => (
"attach_data.thedata" => { contains => [1] },
"classification" => { contains => [1] },
"commenter" => { contains => [1] },
"delta_ts" => { contains => [1] },
percentage_complete => { contains => [1] },
"requestees.login_name" => { contains => [1] },
"setters.login_name" => { contains => [1] },
);
# For changedfrom and changedto.
use constant CHANGED_FROM_TO_BROKEN_NOT => (
'longdescs.count' => { search => 1 },
"bug_group" => { contains => [1] },
"cc" => { contains => [1] },
"estimated_time" => { contains => [1] },
"flagtypes.name" => { contains => [1] },
"keywords" => { contains => [1] },
FIELD_TYPE_MULTI_SELECT, { contains => [1] },
);
# These are field/operator combinations that are broken when run under NOT().
use constant BROKEN_NOT => {
allwords => {
cc => { contains => [1] },
'flagtypes.name' => { contains => [1, 5] },
longdesc => { contains => [1] },
},
'allwords-<1> <2>' => {
cc => { },
},
allwordssubstr => {
cc => { contains => [1] },
'flagtypes.name' => { contains => [5, 6] },
longdesc => { contains => [1] },
},
'allwordssubstr-<1>,<2>' => {
cc => { },
longdesc => { contains => [1] },
},
anyexact => {
'flagtypes.name' => { contains => [1, 2, 5] },
},
'anywords-<1>' => {
'flagtypes.name' => { contains => [1, 2, 3, 4, 5] },
},
'anywords-<1> <2>' => {
'flagtypes.name' => { contains => [3, 4, 5] },
},
anywordssubstr => {
'flagtypes.name' => { contains => [5] },
},
'anywordssubstr-<1> <2>' => {
'flagtypes.name' => { contains => [3,4,5] },
},
casesubstring => {
'flagtypes.name' => { contains => [5] },
},
changedafter => {
"attach_data.thedata" => { contains => [2, 3, 4] },
"classification" => { contains => [2, 3, 4] },
"commenter" => { contains => [2, 3, 4] },
percentage_complete => { contains => [2, 3, 4] },
"delta_ts" => { contains => [2, 3, 4] },
"requestees.login_name" => { contains => [2, 3, 4] },
"setters.login_name" => { contains => [2, 3, 4] },
},
changedbefore => {
CHANGED_BROKEN_NOT,
},
changedby => {
CHANGED_BROKEN_NOT,
creation_ts => { contains => [1] },
work_time => { contains => [1] },
},
changedfrom => {
CHANGED_BROKEN_NOT,
CHANGED_FROM_TO_BROKEN_NOT,
'attach_data.thedata' => { },
blocked => { contains => [1, 2] },
dependson => { contains => [1, 3] },
work_time => { contains => [1] },
FIELD_TYPE_BUG_ID, { contains => [1 .. 4] },
},
changedto => {
CHANGED_BROKEN_NOT,
CHANGED_FROM_TO_BROKEN_NOT,
longdesc => { contains => [1] },
"remaining_time" => { contains => [1] },
},
greaterthan => {
cc => { contains => [1] },
'flagtypes.name' => { contains => [5] },
},
greaterthaneq => {
cc => { contains => [1] },
'flagtypes.name' => { contains => [2, 5] },
},
equals => {
'flagtypes.name' => { contains => [1, 5] },
},
notequals => {
longdesc => { contains => [1] },
},
notregexp => {
longdesc => { contains => [1] },
},
notsubstring => {
longdesc => { contains => [1] },
},
'nowords-<1>' => {
'flagtypes.name' => { contains => [5] },
},
'nowordssubstr-<1>' => {
'flagtypes.name' => { contains => [5] },
},
lessthan => {
'flagtypes.name' => { contains => [5] },
},
lessthaneq => {
'flagtypes.name' => { contains => [1, 5] },
},
regexp => {
'flagtypes.name' => { contains => [1, 5] },
longdesc => { contains => [1] },
},
substring => {
'flagtypes.name' => { contains => [5] },
longdesc => { contains => [1] },
},
};
#############
# Overrides #
#############
# These overrides are used in the TESTS constant, below.
# Regex tests need unique test values for certain fields.
use constant REGEX_OVERRIDE => {
'attachments.mimetype' => { value => '^text/x-1-' },
bug_file_loc => { value => '^http://1-' },
see_also => { value => '^http://1-' },
blocked => { value => '^<1>$' },
dependson => { value => '^<1>$' },
bug_id => { value => '^<1>$' },
'attachments.isobsolete' => { value => '^1'},
'attachments.ispatch' => { value => '^1'},
'attachments.isprivate' => { value => '^1' },
cclist_accessible => { value => '^1' },
reporter_accessible => { value => '^1' },
everconfirmed => { value => '^1' },
'longdescs.count' => { value => '^3' },
'longdescs.isprivate' => { value => '^1' },
creation_ts => { value => '^2037-01-01' },
delta_ts => { value => '^2037-01-01' },
deadline => { value => '^2037-02-01' },
estimated_time => { value => '^1.0' },
remaining_time => { value => '^9.0' },
work_time => { value => '^1.0' },
longdesc => { value => '^1-' },
percentage_complete => { value => '^10' },
FIELD_TYPE_BUG_ID, { value => '^<1>$' },
FIELD_TYPE_DATETIME, { value => '^2037-03-01' }
};
# Common overrides between lessthan and lessthaneq.
use constant LESSTHAN_OVERRIDE => (
alias => { contains => [1,5] },
estimated_time => { contains => [1,5] },
qa_contact => { contains => [1,5] },
resolution => { contains => [1,5] },
status_whiteboard => { contains => [1,5] },
FIELD_TYPE_TEXTAREA, { contains => [1,5] },
FIELD_TYPE_FREETEXT, { contains => [1,5] },
);
# The mandatorily-set fields have values higher than <1>,
# so bug 5 shows up.
use constant GREATERTHAN_OVERRIDE => (
classification => { contains => [2,3,4,5] },
assigned_to => { contains => [2,3,4,5] },
bug_id => { contains => [2,3,4,5] },
bug_group => { contains => [1,2,3,4] },
bug_severity => { contains => [2,3,4,5] },
bug_status => { contains => [2,3,4,5] },
component => { contains => [2,3,4,5] },
commenter => { contains => [2,3,4,5] },
# keywords matches if *any* keyword matches
keywords => { contains => [1,2,3,4] },
longdesc => { contains => [1,2,3,4] },
op_sys => { contains => [2,3,4,5] },
priority => { contains => [2,3,4,5] },
product => { contains => [2,3,4,5] },
reporter => { contains => [2,3,4,5] },
rep_platform => { contains => [2,3,4,5] },
short_desc => { contains => [2,3,4,5] },
version => { contains => [2,3,4,5] },
tag => { contains => [1,2,3,4] },
target_milestone => { contains => [2,3,4,5] },
# Bug 2 is the only bug besides 1 that has a Requestee set.
'requestees.login_name' => { contains => [2] },
FIELD_TYPE_SINGLE_SELECT, { contains => [2,3,4,5] },
# Override SINGLE_SELECT for resolution.
resolution => { contains => [2,3,4] },
# MULTI_SELECTs match if *any* value matches
FIELD_TYPE_MULTI_SELECT, { contains => [1,2,3,4] },
);
# For all positive multi-value types.
use constant MULTI_BOOLEAN_OVERRIDE => (
'attachments.ispatch' => { value => '1,1', contains => [1] },
'attachments.isobsolete' => { value => '1,1', contains => [1] },
'attachments.isprivate' => { value => '1,1', contains => [1] },
cclist_accessible => { value => '1,1', contains => [1] },
reporter_accessible => { value => '1,1', contains => [1] },
'longdescs.isprivate' => { value => '1,1', contains => [1] },
everconfirmed => { value => '1,1', contains => [1] },
);
# Same as above, for negative multi-value types.
use constant NEGATIVE_MULTI_BOOLEAN_OVERRIDE => (
'attachments.ispatch' => { value => '1,1', contains => [2,3,4,5] },
'attachments.isobsolete' => { value => '1,1', contains => [2,3,4,5] },
'attachments.isprivate' => { value => '1,1', contains => [2,3,4,5] },
cclist_accessible => { value => '1,1', contains => [2,3,4,5] },
reporter_accessible => { value => '1,1', contains => [2,3,4,5] },
'longdescs.isprivate' => { value => '1,1', contains => [2,3,4,5] },
everconfirmed => { value => '1,1', contains => [2,3,4,5] },
);
# For anyexact and anywordssubstr
use constant ANY_OVERRIDE => (
'longdescs.count' => { contains => [1,2,3,4] },
'work_time' => { value => '1.0,2.0' },
dependson => { value => '<1>,<3>', contains => [1,3] },
MULTI_BOOLEAN_OVERRIDE,
);
# For all the changed* searches. The ones that have empty contains
# are fields that never change in value, or will never be rationally
# tracked in bugs_activity.
use constant CHANGED_OVERRIDE => (
'attachments.submitter' => { contains => [] },
bug_id => { contains => [] },
reporter => { contains => [] },
tag => { contains => [] },
);
#########
# Tests #
#########
# The basic format of this is a hashref, where the keys are operators,
# and each operator has an arrayref of tests that it runs. The tests
# are hashrefs, with the following possible keys:
#
# contains: This is a list of bug numbers that the search is expected
# to contain. (This is bug numbers, like 1,2,3, not the bug
# ids. For a description of each bug number, see NUM_BUGS.)
# Any bug not listed in "contains" must *not* show up in the
# search result.
# value: The value that you're searching for. There are certain special
# codes that will be replaced with bug values when the tests are
# run. In these examples below, "#" indicates a bug number:
#
# <#> - The field value for this bug.
#
# For any operator that has the string "word" in it, this is
# *all* the values for the current field from the numbered bug,
# joined by a space.
#
# If the operator has the string "substr" in it, then we
# take a substring of the value (for single-value searches)
# or we take a substring of each value and join them (for
# multi-value "word" searches). The length of the substring
# is determined by the SUBSTR_SIZE constants above.)
#
# For other operators, this just becomes the first value from
# the field for the numbered bug.
#
# So, if we were running the "equals" test and checking the
# cc field, <1> would become the login name of the first cc on
# Bug 1. If we did an "anywords" search test, it would become
# a space-separated string of the login names of all the ccs
# on Bug 1. If we did an "anywordssubstr" search test, it would
# become a space-separated string of the first few characters
# of each CC's login name on Bug 1.
#
# <#-id> - The bug id of the numbered bug.
# <#-reporter> - The login name of the numbered bug's reporter.
# <#-delta> - The delta_ts of the numbered bug.
#
# escape: If true, we will call quotemeta() on the value immediately
# before passing it to Search.pm.
#
# transform: A function to call on any field value before inserting
# it for a <#> replacement. The transformation function
# gets all of the bug's values for the field as its arguments.
# if_equal: This allows you to override "contains" for the case where
# the transformed value (from calling the "transform" function)
# is equal to the original value.
#
# override: This allows you to override "contains" and "values" for
# certain fields.
use constant TESTS => {
equals => [
{ contains => [1], value => '<1>' },
],
notequals => [
{ contains => [2,3,4,5], value => '<1>' },
],
substring => [
{ contains => [1], value => '<1>',
override => {
percentage_complete => { contains => [1,2,3] },
}
},
],
casesubstring => [
{ contains => [1], value => '<1>',
override => {
percentage_complete => { contains => [1,2,3] },
}
},
{ contains => [], value => '<1>', transform => sub { lc($_[0]) },
extra_name => 'lc', if_equal => { contains => [1] },
override => {
percentage_complete => { contains => [1,2,3] },
}
},
],
notsubstring => [
{ contains => [2,3,4,5], value => '<1>',
override => {
percentage_complete => { contains => [4,5] },
},
}
],
regexp => [
{ contains => [1], value => '<1>', escape => 1,
override => {
percentage_complete => { value => '^10' },
}
},
{ contains => [1], value => '^1-', override => REGEX_OVERRIDE },
],
notregexp => [
{ contains => [2,3,4,5], value => '<1>', escape => 1,
override => {
percentage_complete => { value => '^10' },
}
},
{ contains => [2,3,4,5], value => '^1-', override => REGEX_OVERRIDE },
],
lessthan => [
{ contains => [1], value => 2,
override => {
# A lot of these contain bug 5 because an empty value is validly
# less than the specified value.
bug_file_loc => { value => 'http://2-', contains => [1,5] },
see_also => { value => 'http://2-' },
'attachments.mimetype' => { value => 'text/x-2-' },
blocked => { value => '<4-id>', contains => [1,2] },
dependson => { value => '<3-id>', contains => [1,3] },
bug_id => { value => '<2-id>' },
'attachments.isprivate' => { value => 1, contains => [2,3,4] },
'attachments.isobsolete' => { value => 1, contains => [2,3,4] },
'attachments.ispatch' => { value => 1, contains => [2,3,4] },
cclist_accessible => { value => 1, contains => [2,3,4,5] },
reporter_accessible => { value => 1, contains => [2,3,4,5] },
'longdescs.count' => { value => 3, contains => [2,3,4,5] },
'longdescs.isprivate' => { value => 1, contains => [1,2,3,4,5] },
everconfirmed => { value => 1, contains => [2,3,4,5] },
creation_ts => { value => '2037-01-02', contains => [1,5] },
delta_ts => { value => '2037-01-02', contains => [1,5] },
deadline => { value => '2037-02-02', contains => [1,5] },
remaining_time => { value => 10, contains => [1,5] },
percentage_complete => { value => 11, contains => [1,5] },
longdesc => { value => '2-', contains => [1,5] },
work_time => { value => 1, contains => [5] },
FIELD_TYPE_BUG_ID, { value => '<2>', contains => [1,5] },
FIELD_TYPE_DATETIME, { value => '2037-03-02', contains => [1,5] },
LESSTHAN_OVERRIDE,
}
},
],
lessthaneq => [
{ contains => [1], value => '<1>',
override => {
'attachments.isobsolete' => { value => 0, contains => [2,3,4] },
'attachments.ispatch' => { value => 0, contains => [2,3,4] },
'attachments.isprivate' => { value => 0, contains => [2,3,4] },
cclist_accessible => { value => 0, contains => [2,3,4,5] },
reporter_accessible => { value => 0, contains => [2,3,4,5] },
'longdescs.count' => { value => 2, contains => [2,3,4,5] },
'longdescs.isprivate' => { value => -1, contains => [] },
everconfirmed => { value => 0, contains => [2,3,4,5] },
bug_file_loc => { contains => [1,5] },
blocked => { contains => [1,2] },
deadline => { contains => [1,5] },
dependson => { contains => [1,3] },
creation_ts => { contains => [1,5] },
delta_ts => { contains => [1,5] },
remaining_time => { contains => [1,5] },
longdesc => { contains => [1,5] },
percentage_complete => { contains => [1,5] },
work_time => { value => 1, contains => [1,5] },
FIELD_TYPE_BUG_ID, { contains => [1,5] },
FIELD_TYPE_DATETIME, { contains => [1,5] },
LESSTHAN_OVERRIDE,
},
},
],
greaterthan => [
{ contains => [2,3,4], value => '<1>',
override => {
dependson => { contains => [3] },
blocked => { contains => [2] },
'attachments.ispatch' => { value => 0, contains => [1] },
'attachments.isobsolete' => { value => 0, contains => [1] },
'attachments.isprivate' => { value => 0, contains => [1] },
cclist_accessible => { value => 0, contains => [1] },
reporter_accessible => { value => 0, contains => [1] },
'longdescs.count' => { value => 2, contains => [1] },
'longdescs.isprivate' => { value => 0, contains => [1] },
everconfirmed => { value => 0, contains => [1] },
'flagtypes.name' => { value => 2, contains => [2,3,4] },
GREATERTHAN_OVERRIDE,
},
},
],
greaterthaneq => [
{ contains => [2,3,4], value => '<2>',
override => {
'attachments.ispatch' => { value => 1, contains => [1] },
'attachments.isobsolete' => { value => 1, contains => [1] },
'attachments.isprivate' => { value => 1, contains => [1] },
cclist_accessible => { value => 1, contains => [1] },
reporter_accessible => { value => 1, contains => [1] },
'longdescs.count' => { value => 3, contains => [1] },
'longdescs.isprivate' => { value => 1, contains => [1] },
everconfirmed => { value => 1, contains => [1] },
dependson => { value => '<3>', contains => [1,3] },
blocked => { contains => [1,2] },
GREATERTHAN_OVERRIDE,
}
},
],
matches => [
{ contains => [1], value => '<1>' },
],
notmatches => [
{ contains => [2,3,4,5], value => '<1>' },
],
anyexact => [
{ contains => [1,2], value => '<1>, <2>',
override => { ANY_OVERRIDE } },
],
anywordssubstr => [
{ contains => [1,2], value => '<1> <2>',
override => {
ANY_OVERRIDE,
percentage_complete => { contains => [1,2,3] },
}
},
],
allwordssubstr => [
{ contains => [1], value => '<1>',
override => {
MULTI_BOOLEAN_OVERRIDE,
# We search just the number "1" for percentage_complete,
# which matches a lot of bugs.
percentage_complete => { contains => [1,2,3] },
},
},
{ contains => [], value => '<1>,<2>',
override => {
dependson => { value => '<1-id> <3-id>', contains => [] },
# bug 3 has the value "21" here, so matches "2,1"
percentage_complete => { value => '<2>,<3>', contains => [3] },
# 1 0 matches bug 1, which has both public and private comments.
'longdescs.isprivate' => { contains => [1] },
}
},
],
nowordssubstr => [
{ contains => [2,3,4,5], value => '<1>',
override => {
# longdescs.isprivate translates to "1 0", so no bugs should
# show up.
'longdescs.isprivate' => { contains => [] },
percentage_complete => { contains => [4,5] },
work_time => { contains => [2,3,4,5] },
}
},
],
anywords => [
{ contains => [1], value => '<1>',
override => {
MULTI_BOOLEAN_OVERRIDE,
}
},
{ contains => [1,2], value => '<1> <2>',
override => {
MULTI_BOOLEAN_OVERRIDE,
dependson => { value => '<1> <3>', contains => [1,3] },
'longdescs.count' => { contains => [1,2,3,4] },
},
},
],
allwords => [
{ contains => [1], value => '<1>',
override => { MULTI_BOOLEAN_OVERRIDE } },
{ contains => [], value => '<1> <2>',
override => {
dependson => { contains => [], value => '<2-id> <3-id>' },
# 1 0 matches bug 1, which has both public and private comments.
'longdescs.isprivate' => { contains => [1] },
}
},
],
nowords => [
{ contains => [2,3,4,5], value => '<1>',
override => {
# longdescs.isprivate translates to "1 0", so no bugs should
# show up.
'longdescs.isprivate' => { contains => [] },
work_time => { contains => [2,3,4,5] },
}
},
],
changedbefore => [
{ contains => [1], value => '<1-delta>',
override => {
CHANGED_OVERRIDE,
creation_ts => { contains => [1,5] },
blocked => { contains => [1,2] },
dependson => { contains => [1,3] },
longdesc => { contains => [1,5] },
'longdescs.count' => { contains => [1,5] },
}
},
],
changedafter => [
{ contains => [2,3,4], value => '<2-delta>',
override => {
CHANGED_OVERRIDE,
creation_ts => { contains => [3,4] },
# We only change this for one bug, and it doesn't match.
'longdescs.isprivate' => { contains => [] },
# Same for everconfirmed.
'everconfirmed' => { contains => [] },
# For blocked and dependson, they have the delta_ts of bug1
# in the bugs_activity table, so they won't ever match.
blocked => { contains => [] },
dependson => { contains => [] },
}
},
],
changedfrom => [
{ contains => [1], value => '<1>',
override => {
CHANGED_OVERRIDE,
# The test never changes an already-set dependency field, but
# we *can* attempt to test searching against an empty value,
# which should get us some bugs.
blocked => { value => '', contains => [1,2] },
dependson => { value => '', contains => [1,3] },
FIELD_TYPE_BUG_ID, { value => '', contains => [1,2,3,4] },
# longdesc changedfrom doesn't make any sense.
longdesc => { contains => [] },
# Nor does creation_ts changedfrom.
creation_ts => { contains => [] },
'attach_data.thedata' => { contains => [] },
bug_id => { value => '<1-id>', contains => [] },
},
},
],
changedto => [
{ contains => [1], value => '<1>',
override => {
CHANGED_OVERRIDE,
# I can't imagine any use for creation_ts changedto.
creation_ts => { contains => [] },
}
},
],
changedby => [
{ contains => [1], value => '<1-reporter>',
override => {
CHANGED_OVERRIDE,
blocked => { contains => [1,2] },
dependson => { contains => [1,3] },
},
},
],
# XXX these need tests developed
isempty => [],
isnotempty => [],
};
# Fields that do not behave as we expect, for InjectionTest.
# search => 1 means the Bugzilla::Search creation fails.
# sql_error is a regex that specifies a SQL error that's OK for us to throw.
# operator_ok overrides the "brokenness" of certain operators, so that they
# are always OK for that field/operator combination.
use constant INJECTION_BROKEN_FIELD => {
# Pg can't run injection tests against integer or date fields. See bug 577557.
'attachments.isobsolete' => { db_skip => ['Pg'] },
'attachments.ispatch' => { db_skip => ['Pg'] },
'attachments.isprivate' => { db_skip => ['Pg'] },
blocked => { db_skip => ['Pg'] },
bug_id => { db_skip => ['Pg'] },
cclist_accessible => { db_skip => ['Pg'] },
creation_ts => { db_skip => ['Pg'] },
days_elapsed => { db_skip => ['Pg'] },
dependson => { db_skip => ['Pg'] },
deadline => { db_skip => ['Pg'] },
delta_ts => { db_skip => ['Pg'] },
estimated_time => { db_skip => ['Pg'] },
everconfirmed => { db_skip => ['Pg'] },
'longdescs.isprivate' => { db_skip => ['Pg'] },
percentage_complete => { db_skip => ['Pg'] },
remaining_time => { db_skip => ['Pg'] },
reporter_accessible => { db_skip => ['Pg'] },
work_time => { db_skip => ['Pg'] },
FIELD_TYPE_BUG_ID, { db_skip => ['Pg'] },
FIELD_TYPE_DATETIME, { db_skip => ['Pg'] },
owner_idle_time => { search => 1 },
'longdescs.count' => {
search => 1,
db_skip => ['Pg'],
operator_ok => [qw(allwords allwordssubstr anywordssubstr casesubstring
changedbefore changedafter greaterthan greaterthaneq
lessthan lessthaneq notregexp notsubstring
nowordssubstr regexp substring anywords
notequals nowords equals anyexact)],
},
};
# Operators that do not behave as we expect, for InjectionTest.
# search => 1 means the Bugzilla::Search creation fails, but
# field_ok contains fields that it does actually succeed for.
use constant INJECTION_BROKEN_OPERATOR => {
changedafter => { search => 1, field_ok => ['creation_ts'] },
changedbefore => { search => 1, field_ok => ['creation_ts'] },
changedby => { search => 1 },
isempty => { search => 1 },
isnotempty => { search => 1 },
};
# Tests run by Bugzilla::Test::Search::InjectionTest.
# We have to make sure the values are all one word or they'll be split
# up by the multi-word tests.
use constant INJECTION_TESTS => (
{ value => ';SEMICOLON_TEST' },
{ value => '--COMMENT_TEST' },
{ value => "'QUOTE_TEST" },
{ value => "';QUOTE_SEMICOLON_TEST" },
{ value => '/*STAR_COMMENT_TEST' }
);
#################
# Special Tests #
#################
use constant SPECIAL_PARAM_TESTS => (
{ field => 'bug_status', operator => 'anyexact', value => '__open__',
contains => [5] },
{ field => 'bug_status', operator => 'anyexact', value => '__closed__',
contains => [1,2,3,4] },
{ field => 'bug_status', operator => 'anyexact', value => '__all__',
contains => [1,2,3,4,5] },
{ field => 'resolution', operator => 'anyexact', value => '---',
contains => [5] },
# email* query parameters.
{ field => 'assigned_to', operator => 'anyexact',
value => '<1>, <2-reporter>', contains => [1,2],
extra_params => { emailreporter1 => 1 } },
{ field => 'assigned_to', operator => 'equals',
value => '<1>', extra_name => 'email2', contains => [],
extra_params => {
email2 => generate_random_password(100), emaillongdesc2 => 1,
},
},
# standard pronouns
{ field => 'assigned_to', operator => 'equals', value => '%assignee%',
contains => [1,2,3,4,5] },
{ field => 'reporter', operator => 'equals', value => '%reporter%',
contains => [1,2,3,4,5] },
{ field => 'qa_contact', operator => 'equals', value => '%qacontact%',
contains => [1,2,3,4,5] },
{ field => 'cc', operator => 'equals', value => '%user%',
contains => [1] },
# group pronouns
{ field => 'reporter', operator => 'equals',
value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] },
{ field => 'assigned_to', operator => 'equals',
value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] },
{ field => 'qa_contact', operator => 'equals',
value => '%group.<1-bug_group>%', contains => [1,2,3,4] },
{ field => 'cc', operator => 'equals',
value => '%group.<1-bug_group>%', contains => [1,2,3,4] },
{ field => 'commenter', operator => 'equals',
value => '%group.<1-bug_group>%', contains => [1,2,3,4,5] },
);
use constant CUSTOM_SEARCH_TESTS => (
{ name => 'OP without CP', contains => [1],
params => [
{ f => 'OP' },
{ f => 'bug_id', o => 'equals', v => '<1>' },
]
},
{ name => 'Empty OP/CP pair before criteria', contains => [1],
params => [
{ f => 'OP' }, { f => 'CP' },
{ f => 'bug_id', o => 'equals', v => '<1>' },
]
},
{ name => 'Empty OP/CP pair after criteria', contains => [1],
params => [
{ f => 'bug_id', o => 'equals', v => '<1>' },
{ f => 'OP' }, { f => 'CP' },
]
},
{ name => 'empty OP/CP mid criteria', contains => [1],
columns => ['assigned_to'],
params => [
{ f => 'bug_id', o => 'equals', v => '<1>' },
{ f => 'OP' }, { f => 'CP' },
{ f => 'assigned_to', o => 'substr', v => '@' },
]
},
{ name => 'bug_id = 1 AND assigned_to contains @', contains => [1],
columns => ['assigned_to'],
params => [
{ f => 'bug_id', o => 'equals', v => '<1>' },
{ f => 'assigned_to', o => 'substr', v => '@' },
]
},
{ name => 'NOT(bug_id = 1) AND NOT(assigned_to = 2)',
contains => [3,4,5],
columns => ['assigned_to'],
params => [
{ n => 1, f => 'bug_id', o => 'equals', v => '<1>' },
{ n => 1, f => 'assigned_to', o => 'equals', v => '<2>' },
]
},
{ name => 'bug_id = 1 OR assigned_to = 2', contains => [1,2],
columns => ['assigned_to'], top_params => { j_top => 'OR' },
params => [
{ f => 'bug_id', o => 'equals', v => '<1>' },
{ f => 'assigned_to', o => 'equals', v => '<2>' },
]
},
{ name => 'NOT(bug_id = 1 AND assigned_to = 1)', contains => [2,3,4,5],
columns => ['assigned_to'],
params => [
{ f => 'OP', n => 1 },
{ f => 'bug_id', o => 'equals', v => '<1>' },
{ f => 'assigned_to', o => 'equals', v => '<1>' },
{ f => 'CP' },
]
},
{ name => '(bug_id = 1 AND assigned_to contains @) '
. ' OR (bug_id = 2 AND assigned_to contains @)',
contains => [1,2], columns => ['assigned_to'],
top_params => { j_top => 'OR' },
params => [
{ f => 'OP' },
{ f => 'bug_id', o => 'equals', v => '<1>' },
{ f => 'assigned_to', o => 'substr', v => '@' },
{ f => 'CP' },
{ f => 'OP' },
{ f => 'bug_id', o => 'equals', v => '<2>' },
{ f => 'assigned_to', o => 'substr', v => '@' },
{ f => 'CP' },
]
},
{ name => '(bug_id = 1 OR assigned_to = 2) '
. ' AND (bug_id = 2 OR assigned_to = 1)',
contains => [1,2], columns => ['assigned_to'],
params => [
{ f => 'OP', j => 'OR' },
{ f => 'bug_id', o => 'equals', v => '<1>' },
{ f => 'assigned_to', o => 'equals', v => '<2>' },
{ f => 'CP' },
{ f => 'OP', j => 'OR' },
{ f => 'bug_id', o => 'equals', v => '<2>' },
{ f => 'assigned_to', o => 'equals', v => '<1>' },
{ f => 'CP' },
]
},
{ name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) '
. ' AND (bug_id = 2 OR assigned_to = 1) )',
contains => [1,2,3], columns => ['assigned_to'],
top_params => { j_top => 'OR' },
params => [
{ f => 'bug_id', o => 'equals', v => '<3>' },
{ f => 'OP' },
{ f => 'OP', j => 'OR' },
{ f => 'bug_id', o => 'equals', v => '<1>' },
{ f => 'assigned_to', o => 'equals', v => '<2>' },
{ f => 'CP' },
{ f => 'OP', j => 'OR' },
{ f => 'bug_id', o => 'equals', v => '<2>' },
{ f => 'assigned_to', o => 'equals', v => '<1>' },
{ f => 'CP' },
{ f => 'CP' },
]
},
{ name => 'bug_id = 3 OR ( (bug_id = 1 OR assigned_to = 2) '
. ' AND (bug_id = 2 OR assigned_to = 1) ) OR bug_id = 4',
contains => [1,2,3,4], columns => ['assigned_to'],
top_params => { j_top => 'OR' },
params => [
{ f => 'bug_id', o => 'equals', v => '<3>' },
{ f => 'OP' },
{ f => 'OP', j => 'OR' },
{ f => 'bug_id', o => 'equals', v => '<1>' },
{ f => 'assigned_to', o => 'equals', v => '<2>' },
{ f => 'CP' },
{ f => 'OP', j => 'OR' },
{ f => 'bug_id', o => 'equals', v => '<2>' },
{ f => 'assigned_to', o => 'equals', v => '<1>' },
{ f => 'CP' },
{ f => 'CP' },
{ f => 'bug_id', o => 'equals', v => '<4>' },
]
},
);
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# This module represents a test with custom URL parameters.
# Tests like this are specified in CUSTOM_SEARCH_TESTS in
# Bugzilla::Test::Search::Constants.
package Bugzilla::Test::Search::CustomTest;
use parent qw(Bugzilla::Test::Search::FieldTest);
use strict;
use warnings;
use Bugzilla::Test::Search::FieldTest;
use Bugzilla::Test::Search::OperatorTest;
use Storable qw(dclone);
###############
# Constructor #
###############
sub new {
my ($class, $test, $search_test) = @_;
bless { raw_test => dclone($test), search_test => $search_test }, $class;
}
#############
# Accessors #
#############
sub search_test { return $_[0]->{search_test} }
sub name { return 'Custom: ' . $_[0]->test->{name} }
sub test { return $_[0]->{raw_test} }
sub operator_test { die "unimplemented" }
sub field_object { die "unimplemented" }
sub main_value { die "unimplenmented" }
sub test_value { die "unimplemented" }
# Custom tests don't use transforms.
sub transformed_value_was_equal { 0 }
sub debug_value {
my ($self) = @_;
my $string = '';
my $params = $self->search_params;
foreach my $param (keys %$params) {
$string .= $param . "=" . $params->{$param} . '&';
}
chop($string);
return $string;
}
# The tests we know are broken for this operator/field combination.
sub _known_broken { return {} }
sub contains_known_broken { return undef }
sub search_known_broken { return undef }
sub field_not_yet_implemented { return undef }
sub invalid_field_operator_combination { return undef }
#########################################
# Accessors: Bugzilla::Search Arguments #
#########################################
# Converts the f, o, v rows into f0, o0, v0, etc. and translates
# the values appropriately.
sub search_params {
my ($self) = @_;
my %params = %{ $self->test->{top_params} || {} };
my $counter = 0;
foreach my $row (@{ $self->test->{params} }) {
$row->{v} = $self->translate_value($row) if exists $row->{v};
foreach my $key (keys %$row) {
$params{"${key}$counter"} = $row->{$key};
}
$counter++;
}
return \%params;
}
sub translate_value {
my ($self, $row) = @_;
my $as_test = { field => $row->{f}, operator => $row->{o},
value => $row->{v} };
my $operator_test = new Bugzilla::Test::Search::OperatorTest($row->{o},
$self->search_test);
my $field = Bugzilla::Field->check($row->{f});
my $field_test = new Bugzilla::Test::Search::FieldTest($operator_test,
$field, $as_test);
return $field_test->translated_value;
}
sub search_columns {
my ($self) = @_;
return ['bug_id', @{ $self->test->{columns} || [] }];
}
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# This module represents the tests that get run on a single
# operator/field combination for Bugzilla::Test::Search.
# This is where all the actual testing happens.
package Bugzilla::Test::Search::FieldTest;
use strict;
use warnings;
use Bugzilla::Search;
use Bugzilla::Test::Search::Constants;
use Bugzilla::Util qw(trim);
use Data::Dumper;
use Scalar::Util qw(blessed);
use Test::More;
use Test::Exception;
###############
# Constructor #
###############
sub new {
my ($class, $operator_test, $field, $test) = @_;
return bless { operator_test => $operator_test,
field_object => $field,
raw_test => $test }, $class;
}
#############
# Accessors #
#############
sub num_tests { return TESTS_PER_RUN }
# The Bugzilla::Test::Search::OperatorTest that this is a child of.
sub operator_test { return $_[0]->{operator_test} }
# The Bugzilla::Field being tested.
sub field_object { return $_[0]->{field_object} }
# The name of the field being tested, which we need much more often
# than we need the object.
sub field {
my ($self) = @_;
$self->{field_name} ||= $self->field_object->name;
return $self->{field_name};
}
# The Bugzilla::Test::Search object that this is a child of.
sub search_test { return $_[0]->operator_test->search_test }
# The operator being tested
sub operator { return $_[0]->operator_test->operator }
# The bugs currently being tested by Bugzilla::Test::Search.
sub bugs { return $_[0]->search_test->bugs }
sub bug {
my $self = shift;
return $self->search_test->bug(@_);
}
sub number {
my ($self, $id) = @_;
foreach my $number (1..NUM_BUGS) {
return $number if $self->search_test->bug($number)->id == $id;
}
return 0;
}
# The name displayed for this test by Test::More. Used in test descriptions.
sub name {
my ($self) = @_;
my $field = $self->field;
my $operator = $self->operator;
my $value = $self->main_value;
my $name = "$field-$operator-$value";
if (my $extra_name = $self->test->{extra_name}) {
$name .= "-$extra_name";
}
return $name;
}
# The appropriate value from the TESTS constant for this test, taking
# into account overrides.
sub test {
my $self = shift;
return $self->{test} if $self->{test};
my %test = %{ $self->{raw_test} };
# We have field name overrides...
my $override = $test{override}->{$self->field};
# And also field type overrides.
if (!$override) {
$override = $test{override}->{$self->field_object->type} || {};
}
foreach my $key (%$override) {
$test{$key} = $override->{$key};
}
$self->{test} = \%test;
return $self->{test};
}
# All the values for all the bugs for this field.
sub _field_values {
my ($self) = @_;
return $self->{field_values} if $self->{field_values};
my %field_values;
foreach my $number (1..NUM_BUGS) {
$field_values{$number} = $self->_field_values_for_bug($number);
}
$self->{field_values} = \%field_values;
return $self->{field_values};
}
# The values for this field for the numbered bug.
sub bug_values {
my ($self, $number) = @_;
return @{ $self->_field_values->{$number} };
}
# The untranslated, non-overriden value--used in the name of the test
# and other places.
sub main_value { return $_[0]->{raw_test}->{value} }
# The untranslated test value, taking into account overrides.
sub test_value { return $_[0]->test->{value} };
# The value translated appropriately for passing to Bugzilla::Search.
sub translated_value {
my $self = shift;
if (!exists $self->{translated_value}) {
my $value = $self->search_test->value_translation_cache($self);
if (!defined $value) {
$value = $self->_translate_value();
$self->search_test->value_translation_cache($self, $value);
}
$self->{translated_value} = $value;
}
return $self->{translated_value};
}
# Used in failure diagnostic messages.
sub debug_fail {
my ($self, $number, $results, $sql) = @_;
my @expected = @{ $self->test->{contains} };
my @results = sort
map { $self->number($_) }
map { $_->[0] }
@$results;
return
" Value: '" . $self->translated_value . "'\n" .
"Expected: [" . join(',', @expected) . "]\n" .
" Results: [" . join(',', @results) . "]\n" .
trim($sql) . "\n";
}
# True for a bug if we ran the "transform" function on it and the
# result was equal to its first value.
sub transformed_value_was_equal {
my ($self, $number, $value) = @_;
if (@_ > 2) {
$self->{transformed_value_was_equal}->{$number} = $value;
$self->search_test->was_equal_cache($self, $number, $value);
}
my $cached = $self->search_test->was_equal_cache($self, $number);
return $cached if defined $cached;
return $self->{transformed_value_was_equal}->{$number};
}
# True if this test is supposed to contain the numbered bug.
sub bug_is_contained {
my ($self, $number) = @_;
my $contains = $self->test->{contains};
if ($self->transformed_value_was_equal($number)
and !$self->test->{override}->{$self->field}->{contains})
{
$contains = $self->test->{if_equal}->{contains};
}
return grep($_ == $number, @$contains) ? 1 : 0;
}
###################################################
# Accessors: Ways of doing SKIP and TODO on tests #
###################################################
# The tests we know are broken for this operator/field combination.
sub _known_broken {
my ($self, $constant, $skip_pg_check) = @_;
$constant ||= KNOWN_BROKEN;
my $field = $self->field;
my $type = $self->field_object->type;
my $operator = $self->operator;
my $value = $self->main_value;
my $value_name = "$operator-$value";
if (my $extra_name = $self->test->{extra_name}) {
$value_name .= "-$extra_name";
}
my $value_broken = $constant->{$value_name}->{$field};
$value_broken ||= $constant->{$value_name}->{$type};
return $value_broken if $value_broken;
my $operator_broken = $constant->{$operator}->{$field};
$operator_broken ||= $constant->{$operator}->{$type};
return $operator_broken if $operator_broken;
return {};
}
# True if the "contains" search for the numbered bug is broken.
# That is, either the result is supposed to contain it and doesn't,
# or the result is not supposed to contain it and does.
sub contains_known_broken {
my ($self, $number) = @_;
my $field = $self->field;
my $operator = $self->operator;
my $contains_broken = $self->_known_broken->{contains} || [];
if (grep($_ == $number, @$contains_broken)) {
return "$field $operator contains $number is known to be broken";
}
return undef;
}
# Used by subclasses. Checks both bug_is_contained and contains_known_broken
# to tell you whether or not the bug will *actually* be found by the test.
sub will_actually_contain_bug {
my ($self, $number) = @_;
my $is_contained = $self->bug_is_contained($number) ? 1 : 0;
my $is_broken = $self->contains_known_broken($number) ? 1 : 0;
# If the test is supposed to contain the bug and *isn't* broken,
# then the test will contain the bug.
return 1 if ($is_contained and !$is_broken);
# If this test is *not* supposed to contain the bug, but that test is
# broken, then this test *will* contain the bug.
return 1 if (!$is_contained and $is_broken);
return 0;
}
# Returns a string if creating a Bugzilla::Search object throws an error,
# with this field/operator/value combination.
sub search_known_broken {
my ($self) = @_;
my $field = $self->field;
my $operator = $self->operator;
if ($self->_known_broken->{search}) {
return "Bugzilla::Search for $field $operator is known to be broken";
}
return undef;
}
# Returns a string if we haven't yet implemented the tests for this field,
# but we plan to in the future.
sub field_not_yet_implemented {
my ($self) = @_;
my $skip_this_field = grep { $_ eq $self->field } SKIP_FIELDS;
if ($skip_this_field) {
my $field = $self->field;
return "$field testing not yet implemented";
}
return undef;
}
# Returns a message if this field/operator combination can't ever be run.
# At no time in the future will this field/operator combination ever work.
sub invalid_field_operator_combination {
my ($self) = @_;
my $field = $self->field;
my $operator = $self->operator;
if ($field eq 'content' && $operator !~ /matches/) {
return "content field does not support $operator";
}
elsif ($operator =~ /matches/ && $field ne 'content') {
return "matches operator does not support fields other than content";
}
return undef;
}
# True if this field is broken in an OR combination.
sub join_broken {
my ($self, $or_broken_map) = @_;
my $or_broken = $or_broken_map->{$self->field . '-' . $self->operator};
if (!$or_broken) {
# See if this is a comment field, and in that case, if there's
# a generic entry for all comment fields.
my $is_comment_field = COMMENT_FIELDS->{$self->field};
if ($is_comment_field) {
$or_broken = $or_broken_map->{'longdescs.-' . $self->operator};
}
}
return $or_broken;
}
#########################################
# Accessors: Bugzilla::Search Arguments #
#########################################
# The data that will get passed to Bugzilla::Search as its arguments.
sub search_params {
my ($self) = @_;
return $self->{search_params} if $self->{search_params};
my %params = (
"field0-0-0" => $self->field,
"type0-0-0" => $self->operator,
"value0-0-0" => $self->translated_value,
);
$self->{search_params} = \%params;
return $self->{search_params};
}
sub search_columns {
my ($self) = @_;
my $field = $self->field;
my @search_fields = qw(bug_id);
if ($self->field_object->buglist) {
my $col_name = COLUMN_TRANSLATION->{$field} || $field;
push(@search_fields, $col_name);
}
return \@search_fields;
}
################
# Field Values #
################
sub _field_values_for_bug {
my ($self, $number) = @_;
my $field = $self->field;
my @values;
if ($field =~ /^attach.+\.(.+)$/ ) {
my $attach_field = $1;
$attach_field = ATTACHMENT_FIELDS->{$attach_field} || $attach_field;
@values = $self->_values_for($number, 'attachments', $attach_field);
}
elsif (my $flag_field = FLAG_FIELDS->{$field}) {
@values = $self->_values_for($number, 'flags', $flag_field);
}
elsif (my $translation = COMMENT_FIELDS->{$field}) {
@values = $self->_values_for($number, 'comments', $translation);
# We want the last value to come first, so that single-value
# searches use the last comment.
@values = reverse @values;
}
elsif ($field eq 'longdescs.count') {
@values = scalar(@{ $self->bug($number)->comments });
}
elsif ($field eq 'work_time') {
@values = $self->_values_for($number, 'actual_time');
}
elsif ($field eq 'bug_group') {
@values = $self->_values_for($number, 'groups_in', 'name');
}
elsif ($field eq 'keywords') {
@values = $self->_values_for($number, 'keyword_objects', 'name');
}
elsif ($field eq 'content') {
@values = $self->_values_for($number, 'short_desc');
}
elsif ($field eq 'see_also') {
@values = $self->_values_for($number, 'see_also', 'name');
}
elsif ($field eq 'tag') {
@values = $self->_values_for($number, 'tags');
}
# Bugzilla::Bug truncates creation_ts, but we need the full value
# from the database. This has no special value for changedfrom,
# because it never changes.
elsif ($field eq 'creation_ts') {
my $bug = $self->bug($number);
my $creation_ts = Bugzilla->dbh->selectrow_array(
'SELECT creation_ts FROM bugs WHERE bug_id = ?',
undef, $bug->id);
@values = ($creation_ts);
}
else {
@values = $self->_values_for($number, $field);
}
# We convert user objects to their login name, here, all in one
# block for simplicity.
if (grep { $_ eq $field } USER_FIELDS) {
# requestees.login_name is empty for most bugs (but checking
# blessed(undef) handles that.
# Values that come from %original_values aren't User objects.
@values = map { blessed($_) ? $_->login : $_ } @values;
@values = grep { defined $_ } @values;
}
return \@values;
}
sub _values_for {
my ($self, $number, $bug_field, $item_field) = @_;
my $item;
if ($self->operator eq 'changedfrom') {
$item = $self->search_test->bug_create_value($number, $bug_field);
}
else {
my $bug = $self->bug($number);
$item = $bug->$bug_field;
}
if ($item_field) {
if ($bug_field eq 'flags' and $item_field eq 'name') {
return (map { $_->name . $_->status } @$item);
}
return (map { $self->_get_item($_, $item_field) } @$item);
}
return @$item if ref($item) eq 'ARRAY';
return $item if defined $item;
return ();
}
sub _get_item {
my ($self, $from, $field) = @_;
if (blessed($from)) {
return $from->$field;
}
return $from->{$field};
}
#####################
# Value Translation #
#####################
# This function translates the "value" specified in TESTS into an actual
# search value to pass to Search.pm. This means that we get the value
# from the current bug (or, in the case of changedfrom, from %original_values)
# and then we insert it as required into the "value" from TESTS. (For example,
# <1> becomes the value for the field from bug 1.)
sub _translate_value {
my $self = shift;
my $value = $self->test_value;
foreach my $number (1..NUM_BUGS) {
$value = $self->_translate_value_for_bug($number, $value);
}
# Sanity check to make sure that none of the <> stuff was left in.
if ($value =~ /<\d/) {
die $self->name . ": value untranslated: $value\n";
}
return $value;
}
sub _translate_value_for_bug {
my ($self, $number, $value) = @_;
my $bug = $self->bug($number);
my $bug_id = $bug->id;
$value =~ s/<$number-id>/$bug_id/g;
my $bug_delta = $bug->delta_ts;
$value =~ s/<$number-delta>/$bug_delta/g;
my $reporter = $bug->reporter->login;
$value =~ s/<$number-reporter>/$reporter/g;
if ($value =~ /<$number-bug_group>/) {
my @bug_groups = map { $_->name } @{ $bug->groups_in };
@bug_groups = grep { $_ =~ /^\d+-group-/ } @bug_groups;
my $group = $bug_groups[0];
$value =~ s/<$number-bug_group>/$group/g;
}
my @bug_values = $self->bug_values($number);
return $value if !@bug_values;
if ($self->operator =~ /substr/) {
@bug_values = map { $self->_substr_value($_) } @bug_values;
}
my $string_value = $bug_values[0];
if ($self->operator =~ /word/) {
$string_value = join(' ', @bug_values);
}
if (my $func = $self->test->{transform}) {
my $transformed = $func->(@bug_values);
my $is_equal = $transformed eq $bug_values[0] ? 1 : 0;
$self->transformed_value_was_equal($number, $is_equal);
$string_value = $transformed;
}
if ($self->test->{escape}) {
$string_value = quotemeta($string_value);
}
$value =~ s/<$number>/$string_value/g;
return $value;
}
sub _substr_value {
my ($self, $value) = @_;
my $field = $self->field;
my $type = $self->field_object->type;
my $substr_size = SUBSTR_SIZE;
if (exists FIELD_SUBSTR_SIZE->{$field}) {
$substr_size = FIELD_SUBSTR_SIZE->{$field};
}
elsif (exists FIELD_SUBSTR_SIZE->{$type}) {
$substr_size = FIELD_SUBSTR_SIZE->{$type};
}
if ($substr_size > 0) {
# The field name is included in every field value, and if it's
# long, it might take up the whole substring, and we don't want that.
if (!grep { $_ eq $field or $_ eq $type } SUBSTR_NO_FIELD_ADD) {
$substr_size += length($field);
}
my $string = substr($value, 0, $substr_size);
return $string;
}
return substr($value, $substr_size);
}
#####################
# Main Test Methods #
#####################
sub run {
my ($self) = @_;
my $invalid_combination = $self->invalid_field_operator_combination;
my $field_not_implemented = $self->field_not_yet_implemented;
SKIP: {
skip($invalid_combination, $self->num_tests) if $invalid_combination;
TODO: {
todo_skip ($field_not_implemented, $self->num_tests) if $field_not_implemented;
$self->do_tests();
}
}
}
sub do_tests {
my ($self) = @_;
my $name = $self->name;
my $search_broken = $self->search_known_broken;
my $search = $self->_test_search_object_creation();
my $sql;
TODO: {
local $TODO = $search_broken if $search_broken;
lives_ok { $sql = $search->_sql } "$name: generate SQL";
}
my $results;
SKIP: {
skip "Can't run SQL without any SQL", 1 if !defined $sql;
$results = $self->_test_sql($search);
}
$self->_test_content($results, $sql);
}
sub _test_search_object_creation {
my ($self) = @_;
my $name = $self->name;
my @args = (fields => $self->search_columns, params => $self->search_params);
my $search;
lives_ok { $search = new Bugzilla::Search(@args) }
"$name: create search object";
return $search;
}
sub _test_sql {
my ($self, $search) = @_;
my $name = $self->name;
my $results;
lives_ok { $results = $search->data } "$name: Run SQL Query"
or diag($search->_sql);
return $results;
}
sub _test_content {
my ($self, $results, $sql) = @_;
SKIP: {
skip "Without results we can't test them", NUM_BUGS if !$results;
foreach my $number (1..NUM_BUGS) {
$self->_test_content_for_bug($number, $results, $sql);
}
}
}
sub _test_content_for_bug {
my ($self, $number, $results, $sql) = @_;
my $name = $self->name;
my $contains_known_broken = $self->contains_known_broken($number);
my %result_ids = map { $_->[0] => 1 } @$results;
my $bug_id = $self->bug($number)->id;
TODO: {
local $TODO = $contains_known_broken if $contains_known_broken;
if ($self->bug_is_contained($number)) {
ok($result_ids{$bug_id},
"$name: contains bug $number ($bug_id)")
or diag $self->debug_fail($number, $results, $sql);
}
else {
ok(!$result_ids{$bug_id},
"$name: does not contain bug $number ($bug_id)")
or diag $self->debug_fail($number, $results, $sql);
}
}
}
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# This is the same as a FieldTest, except that it uses normal URL
# parameters instead of Boolean Charts.
package Bugzilla::Test::Search::FieldTestNormal;
use strict;
use warnings;
use parent qw(Bugzilla::Test::Search::FieldTest);
use Scalar::Util qw(blessed);
use constant CH_OPERATOR => {
changedafter => 'chfieldfrom',
changedbefore => 'chfieldto',
changedto => 'chfieldvalue',
};
use constant EMAIL_FIELDS => qw(assigned_to qa_contact cc reporter commenter);
# Normally, we just clone a FieldTest because that's the best for performance,
# overall--that way we don't have to translate the value again. However,
# sometimes (like in Bugzilla::Test::Search's direct code) we just want
# to create a FieldTestNormal.
sub new {
my $class = shift;
my ($first_arg) = @_;
if (blessed $first_arg
and $first_arg->isa('Bugzilla::Test::Search::FieldTest'))
{
my $self = { %$first_arg };
return bless $self, $class;
}
return $class->SUPER::new(@_);
}
sub name {
my $self = shift;
my $name = $self->SUPER::name(@_);
return "$name (Normal Params)";
}
sub search_columns {
my $self = shift;
my $field = $self->field;
# For the assigned_to, qa_contact, and reporter fields, have the
# "Normal Params" test check that the _realname columns work
# all by themselves.
if (grep($_ eq $field, EMAIL_FIELDS) && $self->field_object->buglist) {
return ['bug_id', "${field}_realname"]
}
return $self->SUPER::search_columns(@_);
}
sub search_params {
my ($self) = @_;
my $field = $self->field;
my $operator = $self->operator;
my $value = $self->translated_value;
if ($operator eq 'anyexact') {
$value = [split ',', $value];
}
if (my $ch_param = CH_OPERATOR->{$operator}) {
if ($field eq 'creation_ts') {
$field = '[Bug creation]';
}
return { chfield => $field, $ch_param => $value };
}
if ($field eq 'delta_ts' and $operator eq 'greaterthaneq') {
return { chfieldfrom => $value };
}
if ($field eq 'delta_ts' and $operator eq 'lessthaneq') {
return { chfieldto => $value };
}
if ($field eq 'deadline' and $operator eq 'greaterthaneq') {
return { deadlinefrom => $value };
}
if ($field eq 'deadline' and $operator eq 'lessthaneq') {
return { deadlineto => $value };
}
if (grep { $_ eq $field } EMAIL_FIELDS) {
$field = 'longdesc' if $field eq 'commenter';
return {
email1 => $value,
"email${field}1" => 1,
emailtype1 => $operator,
# Used to do extra tests on special sorts of email* combinations.
%{ $self->test->{extra_params} || {} },
};
}
$field =~ s/\./_/g;
return { $field => $value, "${field}_type" => $operator };
}
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# This module represents the SQL Injection tests that get run on a single
# operator/field combination for Bugzilla::Test::Search.
package Bugzilla::Test::Search::InjectionTest;
use parent qw(Bugzilla::Test::Search::FieldTest);
use strict;
use warnings;
use Bugzilla::Test::Search::Constants;
use Test::Exception;
sub num_tests { return NUM_SEARCH_TESTS }
sub _known_broken {
my ($self) = @_;
my $operator_broken = INJECTION_BROKEN_OPERATOR->{$self->operator};
# We don't want to auto-vivify $operator_broken and thus make it true.
my @field_ok = $operator_broken ? @{ $operator_broken->{field_ok} || [] }
: ();
$operator_broken = undef if grep { $_ eq $self->field } @field_ok;
my $field_broken = INJECTION_BROKEN_FIELD->{$self->field}
|| INJECTION_BROKEN_FIELD->{$self->field_object->type};
# We don't want to auto-vivify $field_broken and thus make it true.
my @operator_ok = $field_broken ? @{ $field_broken->{operator_ok} || [] }
: ();
$field_broken = undef if grep { $_ eq $self->operator } @operator_ok;
return $operator_broken || $field_broken || {};
}
sub sql_error_ok { return $_[0]->_known_broken->{sql_error} }
# Injection tests only skip fields on certain dbs.
sub field_not_yet_implemented {
my ($self) = @_;
# We use the constant directly because we don't want operator_ok
# or field_ok to stop us.
my $broken = INJECTION_BROKEN_FIELD->{$self->field}
|| INJECTION_BROKEN_FIELD->{$self->field_object->type};
my $skip_for_dbs = $broken->{db_skip};
return undef if !$skip_for_dbs;
my $dbh = Bugzilla->dbh;
if (my ($skip) = grep { $dbh->isa("Bugzilla::DB::$_") } @$skip_for_dbs) {
my $field = $self->field;
return "$field injection testing is not supported with $skip";
}
return undef;
}
# Injection tests don't do translation.
sub translated_value { $_[0]->test_value }
sub name { return "injection-" . $_[0]->SUPER::name; }
# Injection tests don't check content.
sub _test_content {}
sub _test_sql {
my $self = shift;
my ($sql) = @_;
my $dbh = Bugzilla->dbh;
my $name = $self->name;
if (my $error_ok = $self->sql_error_ok) {
throws_ok { $dbh->selectall_arrayref($sql) } $error_ok,
"$name: SQL query dies, as we expect";
return;
}
return $self->SUPER::_test_sql(@_);
}
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# This module runs tests just like a normal FieldTest, AndTest,
# or OrTest, but in a NOT chart instead of a normal chart.
#
# Logically this should be a mixin of some sort so that we can apply
# it to OrTest and AndTest, but without Moose there isn't much of an
# easy way to do that.
package Bugzilla::Test::Search::NotTest;
use parent qw(Bugzilla::Test::Search::FieldTest);
use strict;
use warnings;
use Bugzilla::Test::Search::Constants;
# We just clone a FieldTest because that's the best for performance,
# overall--that way we don't have to translate the value again.
sub new {
my ($class, $field_test) = @_;
my $self = { %$field_test };
return bless $self, $class;
}
#############
# Accessors #
#############
sub name {
my ($self) = @_;
return "NOT(" . $self->SUPER::name . ")";
}
# True if this test is supposed to contain the numbered bug. Reversed for
# NOT tests.
sub bug_is_contained {
my $self = shift;
my ($number) = @_;
# No search ever returns bug 6, because it's protected by security groups
# that the searcher isn't a member of.
return 0 if $number == 6;
return $self->SUPER::bug_is_contained(@_) ? 0 : 1;
}
# NOT tests have their own constant for tracking broken-ness.
sub _known_broken {
my ($self) = @_;
return $self->SUPER::_known_broken(BROKEN_NOT, 'skip pg check');
}
sub search_params {
my ($self) = @_;
my %params = %{ $self->SUPER::search_params() };
$params{negate0} = 1;
return \%params;
}
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# This module represents the tests that get run on a single operator
# from the TESTS constant in Bugzilla::Search::Test::Constants.
package Bugzilla::Test::Search::OperatorTest;
use strict;
use warnings;
use Bugzilla::Test::Search::Constants;
use Bugzilla::Test::Search::FieldTest;
use Bugzilla::Test::Search::FieldTestNormal;
use Bugzilla::Test::Search::InjectionTest;
use Bugzilla::Test::Search::OrTest;
use Bugzilla::Test::Search::AndTest;
use Bugzilla::Test::Search::NotTest;
###############
# Constructor #
###############
sub new {
my ($invocant, $operator, $search_test) = @_;
$search_test ||= $invocant->search_test;
my $class = ref($invocant) || $invocant;
return bless { search_test => $search_test, operator => $operator }, $class;
}
#############
# Accessors #
#############
# The Bugzilla::Test::Search object that this is a child of.
sub search_test { return $_[0]->{search_test} }
# The operator being tested
sub operator { return $_[0]->{operator} }
# The tests that we're going to run on this operator.
sub tests { return @{ TESTS->{$_[0]->operator } } }
# The fields we're going to test for this operator.
sub test_fields { return $_[0]->search_test->all_fields }
sub run {
my ($self) = @_;
foreach my $field ($self->test_fields) {
foreach my $test ($self->tests) {
my $field_test =
new Bugzilla::Test::Search::FieldTest($self, $field, $test);
$field_test->run();
my $normal_test =
new Bugzilla::Test::Search::FieldTestNormal($field_test);
$normal_test->run();
my $not_test = new Bugzilla::Test::Search::NotTest($field_test);
$not_test->run();
next if !$self->search_test->option('long');
# Run the OR tests. This tests every other operator (including
# this operator itself) in combination with every other field,
# in an OR with this operator and field.
foreach my $other_operator ($self->search_test->all_operators) {
$self->run_join_tests($field_test, $other_operator);
}
}
foreach my $test (INJECTION_TESTS) {
my $injection_test =
new Bugzilla::Test::Search::InjectionTest($self, $field, $test);
$injection_test->run();
}
}
}
sub run_join_tests {
my ($self, $field_test, $other_operator) = @_;
my $other_operator_test = $self->new($other_operator);
foreach my $other_test ($other_operator_test->tests) {
foreach my $other_field ($self->test_fields) {
$self->_run_one_join_test($field_test, $other_operator_test,
$other_field, $other_test);
$self->search_test->clean_test_history();
}
}
}
sub _run_one_join_test {
my ($self, $field_test, $other_operator_test, $other_field, $other_test) = @_;
my $other_field_test =
new Bugzilla::Test::Search::FieldTest($other_operator_test,
$other_field, $other_test);
my $or_test = new Bugzilla::Test::Search::OrTest($field_test,
$other_field_test);
$or_test->run();
my $and_test = new Bugzilla::Test::Search::AndTest($field_test,
$other_field_test);
$and_test->run();
}
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# This test combines two field/operator combinations using OR in
# a single boolean chart.
package Bugzilla::Test::Search::OrTest;
use parent qw(Bugzilla::Test::Search::FieldTest);
use Bugzilla::Test::Search::Constants;
use List::MoreUtils qw(all any uniq);
use constant type => 'OR';
###############
# Constructor #
###############
sub new {
my $class = shift;
my $self = { field_tests => [@_] };
return bless $self, $class;
}
#############
# Accessors #
#############
sub field_tests { return @{ $_[0]->{field_tests} } }
sub search_test { ($_[0]->field_tests)[0]->search_test }
sub name {
my ($self) = @_;
my @names = map { $_->name } $self->field_tests;
return join('-' . $self->type . '-', @names);
}
# In an OR test, bugs ARE supposed to be contained if they are contained
# by ANY test.
sub bug_is_contained {
my ($self, $number) = @_;
return any { $_->bug_is_contained($number) } $self->field_tests;
}
# Needed only for failure messages
sub debug_value {
my ($self) = @_;
my @values = map { $_->field . ' ' . $_->debug_value } $self->field_tests;
return join(' ' . $self->type . ' ', @values);
}
########################
# SKIP & TODO Messages #
########################
sub field_not_yet_implemented {
my ($self) = @_;
return $self->_join_messages('field_not_yet_implemented');
}
sub invalid_field_operator_combination {
my ($self) = @_;
return $self->_join_messages('invalid_field_operator_combination');
}
sub search_known_broken {
my ($self) = @_;
return $self->_join_messages('search_known_broken');
}
sub _join_messages {
my ($self, $message_method) = @_;
my @messages = map { $_->$message_method } $self->field_tests;
@messages = grep { $_ } @messages;
return join(' AND ', @messages);
}
sub _bug_will_actually_be_contained {
my ($self, $number) = @_;
foreach my $test ($self->field_tests) {
# Some tests are broken in such a way that they actually
# generate no criteria in the SQL. In this case, the only way
# the test contains the bug is if *another* test contains it.
next if $test->_known_broken->{no_criteria};
return 1 if $test->will_actually_contain_bug($number);
}
return 0;
}
sub contains_known_broken {
my ($self, $number) = @_;
if ( ( $self->bug_is_contained($number)
and !$self->_bug_will_actually_be_contained($number) )
or ( !$self->bug_is_contained($number)
and $self->_bug_will_actually_be_contained($number) ) )
{
my @messages = map { $_->contains_known_broken($number) }
$self->field_tests;
@messages = grep { $_ } @messages;
# Sometimes, with things that break because of no_criteria, there won't
# be anything in @messages even though we need to print out a message.
if (!@messages) {
my @no_criteria = grep { $_->_known_broken->{no_criteria} }
$self->field_tests;
@messages = map { "No criteria generated by " . $_->name }
@no_criteria;
}
die "broken test with no message" if !@messages;
return join(' AND ', @messages);
}
return undef;
}
##############################
# Bugzilla::Search arguments #
##############################
sub search_columns {
my ($self) = @_;
my @columns = map { @{ $_->search_columns } } $self->field_tests;
return [uniq @columns];
}
sub search_params {
my ($self) = @_;
my @all_params = map { $_->search_params } $self->field_tests;
my %params;
my $chart = 0;
foreach my $item (@all_params) {
$params{"field0-0-$chart"} = $item->{'field0-0-0'};
$params{"type0-0-$chart"} = $item->{'type0-0-0'};
$params{"value0-0-$chart"} = $item->{'value0-0-0'};
$chart++;
}
return \%params;
}
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
package QA::REST;
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../../lib", "$RealBin/../../../local/lib/perl5";
use autodie;
use LWP::UserAgent;
use JSON;
use QA::Util;
use parent qw(LWP::UserAgent Exporter);
@QA::REST::EXPORT = qw(
MUST_FAIL
get_rest_client
);
use constant MUST_FAIL => 1;
sub get_rest_client {
my $rest_client = LWP::UserAgent->new( ssl_opts => { verify_hostname => 0 } );
bless($rest_client, 'QA::REST');
my $config = $rest_client->{bz_config} = get_config();
$rest_client->{bz_url} = $config->{browser_url} . '/' . $config->{bugzilla_installation} . '/rest/';
$rest_client->{bz_default_headers} = {'Accept' => 'application/json', 'Content-Type' => 'application/json'};
return $rest_client;
}
sub bz_config { return $_[0]->{bz_config}; }
sub call {
my ($self, $method, $data, $http_verb, $expect_to_fail) = @_;
$http_verb = lc($http_verb || 'GET');
$data //= {};
my %args = %{ $self->{bz_default_headers} };
# We do not pass the API key in the URL, so that it's not logged by the web server.
if ($http_verb eq 'get' && $data->{api_key}) {
$args{'X-BUGZILLA-API-KEY'} = $data->{api_key};
}
elsif ($http_verb ne 'get') {
$args{Content} = encode_json($data);
}
my $response = $self->$http_verb($self->{bz_url} . $method, %args);
my $res = decode_json($response->decoded_content);
if ($response->is_success xor $expect_to_fail) {
return $res;
}
else {
die 'error ' . $res->{code} . ': ' . $res->{message} . "\n";
}
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# -*- Mode: perl; indent-tabs-mode: nil -*-
package QA::RPC;
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../../lib", "$RealBin/../../../local/lib/perl5";
use Data::Dumper;
use QA::Util;
use QA::Tests qw(PRIVATE_BUG_USER create_bug_fields);
use Storable qw(dclone);
use Test::More;
sub bz_config {
my $self = shift;
$self->{bz_config} ||= QA::Util::get_config();
return $self->{bz_config};
}
# True if we're doing calls over GET instead of POST.
sub bz_get_mode { return 0 }
# When doing bz_log_in over GET, we can't actually call User.login,
# we just store credentials here and then pass them as Bugzilla_login
# and Bugzilla_password with every future call until User.logout is called
# (which actually just calls _bz_clear_credentials, under GET).
sub _bz_credentials {
my ($self, $user, $pass) = @_;
if (@_ == 3) {
$self->{_bz_credentials}->{user} = $user;
$self->{_bz_credentials}->{pass} = $pass;
}
return $self->{_bz_credentials};
}
sub _bz_clear_credentials { delete $_[0]->{_bz_credentials} }
################################
# Helpers for RPC test scripts #
################################
sub bz_log_in {
my ($self, $user) = @_;
my $username = $self->bz_config->{"${user}_user_login"};
my $password = $self->bz_config->{"${user}_user_passwd"};
if ($self->bz_get_mode) {
$self->_bz_credentials($username, $password);
return;
}
my $call = $self->bz_call_success(
'User.login', { login => $username, password => $password });
cmp_ok($call->result->{id}, 'gt', 0, $self->TYPE . ": Logged in as $user");
$self->{_bz_credentials}->{token} = $call->result->{token};
}
sub bz_call_success {
my ($self, $method, $orig_args, $test_name) = @_;
my $args = $orig_args ? dclone($orig_args) : {};
if ($self->bz_get_mode and $method eq 'User.logout') {
$self->_bz_clear_credentials();
return;
}
my $call;
# Under XMLRPC::Lite, if we pass undef as the second argument,
# it sends a single param <value />, which shows up as an
# empty string on the Bugzilla side.
if ($self->{_bz_credentials}->{token}) {
$args->{Bugzilla_token} = $self->{_bz_credentials}->{token};
}
if (scalar keys %$args) {
$call = $self->call($method, $args);
}
else {
$call = $self->call($method);
}
$test_name ||= "$method returned successfully";
$self->_handle_undef_response($test_name) if !$call;
ok(!$call->fault, $self->TYPE . ": $test_name")
or diag($call->faultstring);
if ($method eq 'User.logout') {
delete $self->{_bz_credentials}->{token};
}
return $call;
}
sub bz_call_fail {
my ($self, $method, $orig_args, $faultstring, $test_name) = @_;
my $args = $orig_args ? dclone($orig_args) : {};
if ($self->{_bz_credentials}->{token}) {
$args->{Bugzilla_token} = $self->{_bz_credentials}->{token};
}
$test_name ||= "$method failed (as intended)";
my $call = $self->call($method, $args);
$self->_handle_undef_response($test_name) if !$call;
ok($call->fault, $self->TYPE . ": $test_name")
or diag("Returned: " . Dumper($call->result));
if (defined $faultstring) {
cmp_ok(trim($call->faultstring), '=~', $faultstring,
$self->TYPE . ": Got correct fault for $method");
}
ok($call->faultcode
&& (($call->faultcode < 32000 && $call->faultcode > -32000)
# Fault codes 32610 and above are OK because they are errors
# that we expect and test for sometimes.
|| $call->faultcode >= 32610),
$self->TYPE . ': Fault code is set properly')
or diag("Code: " . $call->faultcode
. " Message: " . $call->faultstring);
return $call;
}
sub _handle_undef_response {
my ($self, $test_name) = @_;
my $response = $self->transport->http_response;
die "$test_name:\n", $response->as_string;
}
sub bz_get_products {
my ($self) = @_;
$self->bz_log_in('QA_Selenium_TEST');
my $accessible = $self->bz_call_success('Product.get_accessible_products');
my $prod_call = $self->bz_call_success('Product.get', $accessible->result);
my %products;
foreach my $prod (@{ $prod_call->result->{products} }) {
$products{$prod->{name}} = $prod->{id};
}
$self->bz_call_success('User.logout');
return \%products;
}
sub _string_array { map { random_string() } (1..$_[0]) }
sub bz_create_test_bugs {
my ($self, $second_private) = @_;
my $config = $self->bz_config;
my @whiteboard_strings = _string_array(3);
my @summary_strings = _string_array(3);
my $public_bug = create_bug_fields($config);
$public_bug->{whiteboard} = join(' ', @whiteboard_strings);
$public_bug->{summary} = join(' ', @summary_strings);
my $private_bug = dclone($public_bug);
if ($second_private) {
$private_bug->{product} = 'QA-Selenium-TEST';
$private_bug->{component} = 'QA-Selenium-TEST';
$private_bug->{target_milestone} = 'QAMilestone';
$private_bug->{version} = 'QAVersion';
# Although we don't directly use this, this helps some tests that
# depend on the values in $private_bug.
$private_bug->{creator} = $config->{PRIVATE_BUG_USER . '_user_login'};
}
my @create_bugs = (
{ user => 'editbugs',
args => $public_bug,
test => 'Create a public bug' },
{ user => $second_private ? PRIVATE_BUG_USER : 'editbugs',
args => $private_bug,
test => $second_private ? 'Create a private bug'
: 'Create a second public bug' },
);
my $post_success = sub {
my ($call, $t) = @_;
my $id = $call->result->{id};
$t->{args}->{id} = $id;
};
# Creating the bugs isn't really a test, it's just preliminary work
# for the tests. So we just run it with one of the RPC clients.
$self->bz_run_tests(tests => \@create_bugs, method => 'Bug.create',
post_success => $post_success);
return ($public_bug, $private_bug);
}
sub bz_run_tests {
my ($self, %params) = @_;
# Required params
my $config = $self->bz_config;
my $tests = $params{tests};
my $method = $params{method};
# Optional params
my $post_success = $params{post_success};
my $pre_call = $params{pre_call};
my $former_user = '';
foreach my $t (@$tests) {
# Only logout/login if the user has changed since the last test
# (this saves us LOTS of needless logins).
my $user = $t->{user} || '';
if ($former_user ne $user) {
$self->bz_call_success('User.logout') if $former_user;
$self->bz_log_in($user) if $user;
$former_user = $user;
}
$pre_call->($t, $self) if $pre_call;
if ($t->{error}) {
$self->bz_call_fail($method, $t->{args}, $t->{error}, $t->{test});
}
else {
my $call = $self->bz_call_success($method, $t->{args}, $t->{test});
if ($call->result && $post_success) {
$post_success->($call, $t, $self);
}
}
}
$self->bz_call_success('User.logout') if $former_user;
}
sub bz_test_bug {
my ($self, $fields, $bug, $expect, $t, $creation_time) = @_;
foreach my $field (sort @$fields) {
# "description" is used by Bug.create but comments are not returned
# by Bug.get or Bug.search.
next if $field eq 'description';
my @include = @{ $t->{args}->{include_fields} || [] };
my @exclude = @{ $t->{args}->{exclude_fields} || [] };
if ( (@include and !grep($_ eq $field, @include))
or (@exclude and grep($_ eq $field, @exclude)) )
{
ok(!exists $bug->{$field}, "$field is not included")
or diag Dumper($bug);
next;
}
if ($field =~ /^is_/) {
ok(defined $bug->{$field}, $self->TYPE . ": $field is not null");
is($bug->{$field} ? 1 : 0, $expect->{$field} ? 1 : 0,
$self->TYPE . ": $field has the right boolean value");
}
elsif ($field eq 'cc') {
foreach my $cc_item (@{ $expect->{cc} || [] }) {
ok(grep($_ eq $cc_item, @{ $bug->{cc} }),
$self->TYPE . ": $field contains $cc_item");
}
}
elsif ($field eq 'creation_time' or $field eq 'last_change_time') {
my $creation_day;
# XML-RPC and JSON-RPC have different date formats.
if ($self->isa('QA::RPC::XMLRPC')) {
$creation_day = $creation_time->ymd('');
}
else {
$creation_day = $creation_time->ymd;
}
like($bug->{$field}, qr/^\Q${creation_day}\ET\d\d:\d\d:\d\d/,
$self->TYPE . ": $field has the right format");
}
else {
is_deeply($bug->{$field}, $expect->{$field},
$self->TYPE . ": $field value is correct");
}
}
}
1;
__END__
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# -*- Mode: perl; indent-tabs-mode: nil -*-
package QA::RPC::JSONRPC;
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../../../lib", "$RealBin/../../../../local/lib/perl5";
use QA::RPC;
BEGIN {
our @ISA = qw(QA::RPC);
if (eval { require JSON::RPC::Client }) {
push(@ISA, 'JSON::RPC::Client');
}
else {
require JSON::RPC::Legacy::Client;
push(@ISA, 'JSON::RPC::Legacy::Client');
}
}
use URI::Escape;
use constant DATETIME_REGEX => qr/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ$/;
sub TYPE {
my ($self) = @_;
return $self->bz_get_mode ? 'JSON-RPC GET' : 'JSON-RPC';
}
#################################
# Consistency with XMLRPC::Lite #
#################################
sub ua {
my $self = shift;
if ($self->{ua} and not $self->{ua}->isa('QA::RPC::UserAgent')) {
bless $self->{ua}, 'QA::RPC::UserAgent';
}
return $self->SUPER::ua(@_);
}
sub transport { $_[0]->ua }
sub bz_get_mode {
my ($self, $value) = @_;
$self->{bz_get_mode} = $value if @_ > 1;
return $self->{bz_get_mode};
}
sub _bz_callback {
my ($self, $value) = @_;
$self->{bz_callback} = $value if @_ > 1;
return $self->{bz_callback};
}
sub call {
my $self = shift;
my ($method, $args) = @_;
my %params = ( method => $method );
$params{params} = $args ? [$args] : [];
my $config = $self->bz_config;
my $url = $config->{browser_url} . "/"
. $config->{bugzilla_installation} . "/jsonrpc.cgi";
my $result;
if ($self->bz_get_mode) {
my $method_escaped = uri_escape($method);
$url .= "?method=$method_escaped";
if (my $cred = $self->_bz_credentials) {
$args->{Bugzilla_login} = $cred->{user}
if !exists $args->{Bugzilla_login};
$args->{Bugzilla_password} = $cred->{pass}
if !exists $args->{Bugzilla_password};
}
if ($args) {
my $params_json = $self->json->encode($args);
my $params_escaped = uri_escape($params_json);
$url .= "&params=$params_escaped";
}
if ($self->version eq '1.1') {
$url .= "&version=1.1";
}
my $callback = delete $args->{callback};
if (defined $callback) {
$self->_bz_callback($callback);
$url .= "&callback=" . uri_escape($callback);
}
$result = $self->SUPER::call($url);
}
else {
$result = $self->SUPER::call($url, \%params);
}
if ($result) {
bless $result, 'QA::RPC::JSONRPC::ReturnObject';
}
return $result;
}
sub _get {
my $self = shift;
my $result = $self->SUPER::_get(@_);
# Simple JSONP support for tests. We just remove the callback from
# the return value.
my $callback = $self->_bz_callback;
if (defined $callback and $result->is_success) {
my $content = $result->content;
$content =~ s/^(?:\/\*\*\/)?\Q$callback(\E(.*)\)$/$1/s;
$result->content($content);
# We don't need this anymore, and we don't want it to affect
# future calls.
delete $self->{bz_callback};
}
return $result;
}
1;
package QA::RPC::JSONRPC::ReturnObject;
use strict;
BEGIN {
if (eval { require JSON::RPC::Client }) {
our @ISA = qw(JSON::RPC::ReturnObject);
}
else {
require JSON::RPC::Legacy::Client;
our @ISA = qw(JSON::RPC::Legacy::ReturnObject);
}
}
#################################
# Consistency with XMLRPC::Lite #
#################################
sub faultstring { $_[0]->{content}->{error}->{message} }
sub faultcode { $_[0]->{content}->{error}->{code} }
sub fault { $_[0]->is_error }
1;
package QA::RPC::UserAgent;
use strict;
use base qw(LWP::UserAgent);
########################################
# Consistency with XMLRPC::Lite's ->ua #
########################################
sub send_request {
my $self = shift;
my $response = $self->SUPER::send_request(@_);
$self->http_response($response);
# JSON::RPC::Client can't handle 500 responses, even though
# they're required by the JSON-RPC spec.
$response->code(200);
return $response;
}
# Copied directly from SOAP::Lite::Transport::HTTP.
sub http_response {
my $self = shift;
if (@_) { $self->{'_http_response'} = shift; return $self }
return $self->{'_http_response'};
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# -*- Mode: perl; indent-tabs-mode: nil -*-
package QA::RPC::XMLRPC;
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../../../lib", "$RealBin/../../../../local/lib/perl5";
use base qw(QA::RPC XMLRPC::Lite);
use constant TYPE => 'XML-RPC';
use constant DATETIME_REGEX => qr/^\d{8}T\d\d:\d\d:\d\d$/;
1;
__END__
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# -*- Mode: perl; indent-tabs-mode: nil -*-
package QA::Tests;
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../../lib", "$RealBin/../../../local/lib/perl5";
use base qw(Exporter);
our @EXPORT_OK = qw(
PRIVATE_BUG_USER
STANDARD_BUG_TESTS
bug_tests
create_bug_fields
);
use constant INVALID_BUG_ID => -1;
use constant INVALID_BUG_ALIAS => 'aaaaaaa12345';
use constant PRIVATE_BUG_USER => 'QA_Selenium_TEST';
use constant CREATE_BUG => {
'priority' => 'Highest',
'status' => 'CONFIRMED',
'version' => 'unspecified',
'creator' => 'editbugs',
'description' => '-- Comment Created By Bugzilla XML-RPC Tests --',
'cc' => ['unprivileged'],
'component' => 'c1',
'platform' => 'PC',
# It's necessary to assign the bug to somebody who isn't in the
# timetracking group, for the Bug.update tests.
'assigned_to' => PRIVATE_BUG_USER,
'summary' => 'WebService Test Bug',
'product' => 'Another Product',
'op_sys' => 'Linux',
'severity' => 'normal',
'qa_contact' => 'canconfirm',
version => 'Another1',
url => 'http://www.bugzilla.org/',
target_milestone => 'AnotherMS1',
};
sub create_bug_fields {
my ($config) = @_;
my %bug = %{ CREATE_BUG() };
foreach my $field (qw(creator assigned_to qa_contact)) {
my $value = $bug{$field};
$bug{$field} = $config->{"${value}_user_login"};
}
$bug{cc} = [map { $config->{$_ . "_user_login"} } @{ $bug{cc} }];
return \%bug;
}
sub bug_tests {
my ($public_id, $private_id) = @_;
return [
{ args => { ids => [$private_id] },
error => "You are not authorized to access",
test => 'Logged-out user cannot access a private bug',
},
{ args => { ids => [$public_id] },
test => 'Logged-out user can access a public bug.',
},
{ args => { ids => [INVALID_BUG_ID] },
error => "not a valid bug number",
test => 'Passing invalid bug id returns error "Invalid Bug ID"',
},
{ args => { ids => [undef] },
error => "You must enter a valid bug number",
test => 'Passing undef as bug id param returns error "Invalid Bug ID"',
},
{ args => { ids => [INVALID_BUG_ALIAS] },
error => "nor an alias to a bug",
test => 'Passing invalid bug alias returns error "Invalid Bug Alias"',
},
{ user => 'editbugs',
args => { ids => [$private_id] },
error => "You are not authorized to access",
test => 'Access to a private bug is denied to a user without privs',
},
{ user => 'unprivileged',
args => { ids => [$public_id] },
test => 'User without privs can access a public bug',
},
{ user => 'admin',
args => { ids => [$public_id] },
test => 'Admin can access a public bug.',
},
{ user => PRIVATE_BUG_USER,
args => { ids => [$private_id] },
test => 'User with privs can successfully access a private bug',
},
# This helps webservice_bug_attachment get private attachment ids
# from the public bug, and doesn't hurt for the other tests.
{ user => PRIVATE_BUG_USER,
args => { ids => [$public_id] },
test => 'User with privs can also access the public bug',
},
];
}
use constant STANDARD_BUG_TESTS => bug_tests('public_bug', 'private_bug');
1;
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# -*- Mode: perl; indent-tabs-mode: nil -*-
package QA::Util;
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../../lib", "$RealBin/../../../local/lib/perl5";
use autodie;
use Data::Dumper;
use Test::More;
use parent qw(Exporter);
@QA::Util::EXPORT = qw(
trim
url_quote
random_string
log_in
logout
file_bug_in_product
create_bug
edit_bug
edit_bug_and_return
go_to_bug
go_to_home
go_to_admin
edit_product
add_product
open_advanced_search_page
set_parameters
get_selenium
get_rpc_clients
get_config
WAIT_TIME
CHROME_MODE
);
# How long we wait for pages to load.
use constant WAIT_TIME => 60000;
use constant CONF_FILE => "$RealBin/../config/selenium_test.conf";
use constant CHROME_MODE => 1;
use constant NDASH => chr(0x2013);
#####################
# Utility Functions #
#####################
sub random_string {
my $size = shift || 30; # default to 30 chars if nothing specified
return join("", map{ ('0'..'9','a'..'z','A'..'Z')[rand 62] } (1..$size));
}
# Remove consecutive as well as leading and trailing whitespaces.
sub trim {
my ($str) = @_;
if ($str) {
$str =~ s/[\r\n\t\s]+/ /g;
$str =~ s/^\s+//g;
$str =~ s/\s+$//g;
}
return $str;
}
# This originally came from CGI.pm, by Lincoln D. Stein
sub url_quote {
my ($toencode) = (@_);
$toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg;
return $toencode;
}
###################
# Setup Functions #
###################
sub get_config {
# read the test configuration file
my $conf_file = CONF_FILE;
my $config = do($conf_file)
or die "can't read configuration '$conf_file': $!$@";
return $config;
}
sub get_selenium {
my $chrome_mode = shift;
my $config = get_config();
require Test::WWW::Selenium;
require WWW::Selenium::Util;
if (!WWW::Selenium::Util::server_is_running()) {
die "Selenium Server isn't running!";
}
my $sel = Test::WWW::Selenium->new(
host => $config->{host},
port => $config->{port},
browser => $chrome_mode ? $config->{experimental_browser_launcher} : $config->{browser},
browser_url => $config->{browser_url}
);
return ($sel, $config);
}
sub get_xmlrpc_client {
my $config = get_config();
my $xmlrpc_url = $config->{browser_url} . "/" .
$config->{bugzilla_installation} . "/xmlrpc.cgi";
require QA::RPC::XMLRPC;
my $rpc = QA::RPC::XMLRPC->new(proxy => $xmlrpc_url);
return ($rpc, $config);
}
sub get_jsonrpc_client {
my ($get_mode) = @_;
require QA::RPC::JSONRPC;
my $rpc = QA::RPC::JSONRPC->new();
# If we don't set a long timeout, then the Bug.add_comment test
# where we add a too-large comment fails.
$rpc->transport->timeout(180);
$rpc->version($get_mode ? '1.1' : '1.0');
$rpc->bz_get_mode($get_mode);
return $rpc;
}
sub get_rpc_clients {
my ($xmlrpc, $config) = get_xmlrpc_client();
my $jsonrpc = get_jsonrpc_client();
my $jsonrpc_get = get_jsonrpc_client('GET');
return ($config, $xmlrpc, $jsonrpc, $jsonrpc_get);
}
################################
# Helpers for Selenium Scripts #
################################
sub go_to_home {
my ($sel, $config) = @_;
$sel->open_ok("/$config->{bugzilla_installation}/", undef, "Go to the home page");
$sel->title_is("Bugzilla Main Page");
}
# Go to the home/login page and log in.
sub log_in {
my ($sel, $config, $user) = @_;
go_to_home($sel, $config);
$sel->type_ok("Bugzilla_login_top", $config->{"${user}_user_login"}, "Enter $user login name");
$sel->type_ok("Bugzilla_password_top", $config->{"${user}_user_passwd"}, "Enter $user password");
$sel->click_ok("log_in_top", undef, "Submit credentials");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bugzilla Main Page", "User is logged in");
}
# Log out. Will fail if you are not logged in.
sub logout {
my $sel = shift;
$sel->click_ok("link=Log out", undef, "Logout");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Logged Out");
}
# Display the bug form to enter a bug in the given product.
sub file_bug_in_product {
my ($sel, $product, $classification) = @_;
$classification ||= "Unclassified";
$sel->click_ok("link=New", undef, "Go create a new bug");
$sel->wait_for_page_to_load(WAIT_TIME);
my $title = $sel->get_title();
if ($sel->is_text_present("Select Classification")) {
ok(1, "More than one enterable classification available. Display them in a list");
$sel->click_ok("link=$classification", undef, "Choose $classification");
$sel->wait_for_page_to_load(WAIT_TIME);
$title = $sel->get_title();
}
if ($title eq "Enter Bug") {
ok(1, "Display the list of enterable products");
$sel->click_ok("link=$product", undef, "Choose $product");
$sel->wait_for_page_to_load(WAIT_TIME);
}
else {
ok(1, "Only one product available in $classification. Skipping the 'Choose product' page.")
}
$sel->title_is("Enter Bug: $product", "Display form to enter bug data");
}
sub create_bug {
my ($sel, $bug_summary) = @_;
my $ndash = NDASH;
$sel->click_ok('commit');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
my $bug_id = $sel->get_value('//input[@name="id" and @type="hidden"]');
$sel->title_like(qr/$bug_id $ndash( \(.*\))? $bug_summary/, "Bug $bug_id created with summary '$bug_summary'");
return $bug_id;
}
sub edit_bug {
my ($sel, $bug_id, $bug_summary, $options) = @_;
my $ndash = NDASH;
my $btn_id = $options ? $options->{id} : 'commit';
$sel->click_ok($btn_id);
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("$bug_id $ndash $bug_summary", "Changes submitted to bug $bug_id");
# If the web browser doesn't support history.ReplaceState or has it turned off,
# "Bug FIXME processed" is displayed instead (as in Bugzilla 4.0 and older).
# $sel->title_is("Bug $bug_id processed", "Changes submitted to bug $bug_id");
}
sub edit_bug_and_return {
my ($sel, $bug_id, $bug_summary, $options) = @_;
my $ndash = NDASH;
edit_bug($sel, $bug_id, $bug_summary, $options);
$sel->click_ok("//a[contains(\@href, 'show_bug.cgi?id=$bug_id')]");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("$bug_id $ndash $bug_summary", "Returning back to bug $bug_id");
}
# Go to show_bug.cgi.
sub go_to_bug {
my ($sel, $bug_id) = @_;
$sel->type_ok("quicksearch_top", $bug_id);
$sel->click_ok("find_top", undef, "Go to bug $bug_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
my $bug_title = $sel->get_title();
utf8::encode($bug_title) if utf8::is_utf8($bug_title);
$sel->title_like(qr/^$bug_id /, $bug_title);
}
# Go to admin.cgi.
sub go_to_admin {
my $sel = shift;
$sel->click_ok("link=Administration", undef, "Go to the Admin page");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_like(qr/^Administer your installation/, "Display admin.cgi");
}
# Go to editproducts.cgi and display the given product.
sub edit_product {
my ($sel, $product, $classification) = @_;
$classification ||= "Unclassified";
go_to_admin($sel);
$sel->click_ok("link=Products", undef, "Go to the Products page");
$sel->wait_for_page_to_load(WAIT_TIME);
my $title = $sel->get_title();
if ($title eq "Select Classification") {
ok(1, "More than one enterable classification available. Display them in a list");
$sel->click_ok("link=$classification", undef, "Choose $classification");
$sel->wait_for_page_to_load(WAIT_TIME);
}
else {
$sel->title_is("Select product", "Display the list of enterable products");
}
$sel->click_ok("link=$product", undef, "Choose $product");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Product '$product'", "Display properties of $product");
}
sub add_product {
my ($sel, $classification) = @_;
$classification ||= "Unclassified";
go_to_admin($sel);
$sel->click_ok("link=Products", undef, "Go to the Products page");
$sel->wait_for_page_to_load(WAIT_TIME);
my $title = $sel->get_title();
if ($title eq "Select Classification") {
ok(1, "More than one enterable classification available. Display them in a list");
$sel->click_ok("//a[contains(\@href, 'editproducts.cgi?action=add&classification=$classification')]",
undef, "Add product to $classification");
}
else {
$sel->title_is("Select product", "Display the list of enterable products");
$sel->click_ok("link=Add", undef, "Add a new product");
}
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Add Product", "Display the new product form");
}
sub open_advanced_search_page {
my $sel = shift;
$sel->click_ok("link=Search");
$sel->wait_for_page_to_load(WAIT_TIME);
my $title = $sel->get_title();
if ($title eq "Simple Search") {
ok(1, "Display the simple search form");
$sel->click_ok("link=Advanced Search");
$sel->wait_for_page_to_load(WAIT_TIME);
}
$sel->title_is("Search for bugs", "Display the Advanced search form");
}
# $params is a hashref of the form:
# {section1 => { param1 => {type => '(text|select)', value => 'foo'},
# param2 => {type => '(text|select)', value => 'bar'},
# param3 => undef },
# section2 => { param4 => ...},
# }
# section1, section2, ... is the name of the section
# param1, param2, ... is the name of the parameter (which must belong to the given section)
# type => 'text' is for text fields
# type => 'select' is for drop-down select fields
# undef is for radio buttons (in which case the parameter must be the ID of the radio button)
# value => 'foo' is the value of the parameter (either text or label)
sub set_parameters {
my ($sel, $params) = @_;
go_to_admin($sel);
$sel->click_ok("link=Parameters", undef, "Go to the Config Parameters page");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Configuration: Required Settings");
my $last_section = "Required Settings";
foreach my $section (keys %$params) {
if ($section ne $last_section) {
$sel->click_ok("link=$section");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Configuration: $section");
$last_section = $section;
}
my $param_list = $params->{$section};
foreach my $param (keys %$param_list) {
my $data = $param_list->{$param};
if (defined $data) {
my $type = $data->{type};
my $value = $data->{value};
if ($type eq 'text') {
$sel->type_ok($param, $value);
}
elsif ($type eq 'select') {
$sel->select_ok($param, "label=$value");
}
else {
ok(0, "Unknown parameter type: $type");
}
}
else {
# If the value is undefined, then the param name is
# the ID of the radio button.
$sel->click_ok($param);
}
}
$sel->click_ok('//input[@type="submit" and @value="Save Changes"]', undef, "Save Changes");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Parameters Updated");
}
}
1;
__END__
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#######################################
# Tests for REST calls in Bugzilla.pm #
#######################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More tests => 11;
use QA::REST;
my $rest = get_rest_client();
my $config = $rest->bz_config;
my $version = $rest->call('version')->{version};
ok($version, "GET /rest/version returns $version");
my $extensions = $rest->call('extensions')->{extensions};
isa_ok($extensions, 'HASH', 'GET /rest/extensions');
my @ext_names = sort keys %$extensions;
# There is always at least the QA extension enabled.
ok(scalar(@ext_names), scalar(@ext_names) . ' extension(s) found: ' . join(', ', @ext_names));
ok($extensions->{QA}, 'The QA extension is enabled, with version ' . $extensions->{QA}->{version});
my $timezone = $rest->call('timezone')->{timezone};
ok($timezone, "GET /rest/timezone retuns $timezone");
my $time = $rest->call('time');
foreach my $type (qw(db_time web_time)) {
ok($time->{$type}, "GET /rest/time returns $type = " . $time->{$type});
}
# Logged-out users can only access the maintainer and requirelogin parameters.
my $params = $rest->call('parameters')->{parameters};
my @param_names = sort keys %$params;
ok(@param_names == 2 && defined $params->{maintainer} && defined $params->{requirelogin},
'Only 2 parameters accessible to logged-out users: ' . join(', ', @param_names));
# Powerless users can access much more parameters.
$params = $rest->call('parameters', { api_key => $config->{unprivileged_user_api_key} })->{parameters};
@param_names = sort keys %$params;
ok(@param_names > 2, scalar(@param_names) . ' parameters accessible to powerless users');
# Admins can access all parameters.
$params = $rest->call('parameters', { api_key => $config->{admin_user_api_key} })->{parameters};
@param_names = sort keys %$params;
ok(@param_names > 2, scalar(@param_names) . ' parameters accessible to admins');
my $timestamp = $rest->call('last_audit_time')->{last_audit_time};
ok($timestamp, "GET /rest/last_audit_time returns $timestamp");
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#############################################
# Tests for REST calls in Classification.pm #
#############################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More tests => 7;
use QA::REST;
my $rest = get_rest_client();
my $config = $rest->bz_config;
my $args = { api_key => $config->{admin_user_api_key} };
my $params = $rest->call('parameters', $args)->{parameters};
my $use_class = $params->{useclassification};
ok(defined($use_class), 'Classifications are ' . ($use_class ? 'enabled' : 'disabled'));
# Admins can always access classifications, even when they are disabled.
my $class = $rest->call('classification/1', $args)->{classifications}->[0];
ok($class->{id}, "Admin found classification '" . $class->{name} . "' with the description '" . $class->{description} . "'");
my @products = sort map { $_->{name} } @{ $class->{products} };
ok(scalar(@products), scalar(@products) . ' product(s) found: ' . join(', ', @products));
$class = $rest->call('classification/Class2_QA', $args)->{classifications}->[0];
ok($class->{id}, "Admin found classification '" . $class->{name} . "' with the description '" . $class->{description} . "'");
@products = sort map { $_->{name} } @{ $class->{products} };
ok(scalar(@products), scalar(@products) . ' product(s) found: ' . join(', ', @products));
if ($use_class) {
# When classifications are enabled, everybody can query classifications...
# ... including logged-out users.
$class = $rest->call('classification/1')->{classifications}->[0];
ok($class->{id}, 'Logged-out users can access classification ' . $class->{name});
# ... and non-admins.
$class = $rest->call('classification/1', { api_key => $config->{editbugs_user_api_key} })->{classifications}->[0];
ok($class->{id}, 'Non-admins can access classification ' . $class->{name});
}
else {
# When classifications are disabled, only users in the 'editclassifications'
# group can access this method...
# ... logged-out users get an error.
my $error = $rest->call('classification/1', undef, undef, MUST_FAIL);
ok($error->{error} && $error->{code} == 900,
'Logged-out users cannot query classifications when disabled: ' . $error->{message});
# ... as well as non-admins.
$error = $rest->call('classification/1', { api_key => $config->{editbugs_user_api_key} }, undef, MUST_FAIL);
ok($error->{error} && $error->{code} == 900,
'Non-admins cannot query classifications when disabled: ' . $error->{message});
}
#!/usr/bin/perl -w
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
# For a description of this test, see Bugzilla::Test::Search
# in xt/lib/.
use strict;
use warnings;
use lib qw(. xt/lib lib local/lib/perl5);
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Test::Search;
use Getopt::Long;
use Pod::Usage;
use Test::More;
my %switches;
GetOptions(\%switches, 'operators=s', 'top-operators=s', 'long',
'add-custom-fields', 'help|h') || die $@;
pod2usage(verbose => 1) if $switches{'help'};
plan skip_all => "BZ_WRITE_TESTS environment variable not set"
if !$ENV{BZ_WRITE_TESTS};
Bugzilla->usage_mode(USAGE_MODE_TEST);
my $test = new Bugzilla::Test::Search(\%switches);
plan tests => $test->num_tests;
$test->run();
__END__
=head1 NAME
search.t - Test L<Bugzilla::Search>
=head1 DESCRIPTION
This test tests L<Bugzilla::Search>.
Note that users may be prevented from writing new bugs, products, components,
etc. to your database while this test is running.
=head1 OPTIONS
=over
=item --long
Run AND and OR tests in addition to normal tests. Specifying
--long without also specifying L</--top-operators> is likely to
run your system out of memory.
=item --add-custom-fields
This adds every type of custom field to the database, so that they can
all be tested. Note that this B<CANNOT BE REVERSED>, so do not use this
switch on a production installation.
=item --operators=a,b,c
Limit the test to testing only the listed operators.
=item --top-operators=a,b,c
Limit the top-level tested operators to the following list. This
means that for normal tests, only the listed operators will be tested.
However, for OR and AND tests, all other operators will be tested
along with the operators you listed.
=item --help
Display this help.
=back
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
set_parameters($sel, { "Bug Fields" => {"usestatuswhiteboard-on" => undef} });
# Clear the saved search, in case this test didn't complete previously.
if ($sel->is_text_present("My bugs from QA_Selenium")) {
$sel->click_ok("link=My bugs from QA_Selenium");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: My bugs from QA_Selenium");
$sel->click_ok("forget_search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search is gone");
$sel->is_text_present_ok("OK, the My bugs from QA_Selenium search is gone");
}
# Just in case the test failed before completion previously, reset the CANEDIT bit.
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=Master");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Group: Master");
my $group_url = $sel->get_location();
$group_url =~ /group=(\d+)$/;
my $master_gid = $1;
clear_canedit_on_testproduct($sel, $master_gid);
logout($sel);
# First create a bug.
log_in($sel, $config, 'QA_Selenium_TEST');
file_bug_in_product($sel, 'TestProduct');
my $bug_summary = "Test bug editing";
$sel->select_ok("bug_severity", "label=critical");
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "ploc");
my $bug1_id = create_bug($sel, $bug_summary);
# Now edit field values of the bug you just filed.
$sel->select_ok("rep_platform", "label=Other");
$sel->select_ok("op_sys", "label=Other");
$sel->select_ok("priority", "label=Highest");
$sel->select_ok("bug_severity", "label=blocker");
$sel->type_ok("bug_file_loc", "foo.cgi?action=bar");
$sel->type_ok("status_whiteboard", "[Selenium was here]");
$sel->type_ok("comment", "new comment from me :)");
$sel->select_ok("bug_status", "label=RESOLVED");
edit_bug($sel, $bug1_id, $bug_summary);
# Now move the bug into another product, which has a mandatory group.
$sel->click_ok("link=$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
$sel->select_ok("product", "label=QA-Selenium-TEST");
$sel->type_ok("comment", "moving to QA-Selenium-TEST");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Verify New Product Details...");
$sel->select_ok("component", "label=QA-Selenium-TEST");
$sel->is_element_present_ok('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]');
ok(!$sel->is_editable('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]'), "QA-Selenium-TEST group not editable");
$sel->is_checked_ok('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]');
edit_bug_and_return($sel, $bug1_id, $bug_summary, {id => "change_product"});
$sel->select_ok("bug_severity", "label=normal");
$sel->select_ok("priority", "label=High");
$sel->select_ok("rep_platform", "label=All");
$sel->select_ok("op_sys", "label=All");
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", $config->{admin_user_login});
$sel->type_ok("comment", "Unchecking the reporter_accessible checkbox");
# This checkbox is checked by default.
$sel->click_ok("reporter_accessible");
$sel->select_ok("bug_status", "label=VERIFIED");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->type_ok("comment", "I am the reporter, but I can see the bug anyway as I belong to the mandatory group");
edit_bug($sel, $bug1_id, $bug_summary);
logout($sel);
# The admin is not in the mandatory group, but he has been CC'ed,
# so he can view and edit the bug (as he has editbugs privs by inheritance).
log_in($sel, $config, 'admin');
go_to_bug($sel, $bug1_id);
$sel->select_ok("bug_severity", "label=blocker");
$sel->select_ok("priority", "label=Highest");
$sel->type_ok("status_whiteboard", "[Selenium was here][admin too]");
$sel->select_ok("bug_status", "label=CONFIRMED");
$sel->click_ok("bz_assignee_edit_action");
$sel->type_ok("assigned_to", $config->{admin_user_login});
$sel->type_ok("comment", "I have editbugs privs. Taking!");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", $config->{unprivileged_user_login});
edit_bug($sel, $bug1_id, $bug_summary);
logout($sel);
# The powerless user can see the restricted bug, as he has been CC'ed.
log_in($sel, $config, 'unprivileged');
go_to_bug($sel, $bug1_id);
$sel->is_text_present_ok("I have editbugs privs. Taking!");
logout($sel);
# Now turn off cclist_accessible, which will prevent
# the powerless user to see the bug again.
log_in($sel, $config, 'admin');
go_to_bug($sel, $bug1_id);
$sel->click_ok("cclist_accessible");
$sel->type_ok("comment", "I am allowed to turn off cclist_accessible despite not being in the mandatory group");
edit_bug($sel, $bug1_id, $bug_summary);
logout($sel);
# The powerless user cannot see the restricted bug anymore.
log_in($sel, $config, 'unprivileged');
$sel->type_ok("quicksearch_top", $bug1_id);
$sel->click_ok("find_top");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug Access Denied");
$sel->is_text_present_ok("You are not authorized to access bug #$bug1_id");
logout($sel);
# Move the bug back to TestProduct, which has no group restrictions.
log_in($sel, $config, 'admin');
go_to_bug($sel, $bug1_id);
$sel->select_ok("product", "label=TestProduct");
# When selecting a new product, Bugzilla tries to reassign the bug by default,
# so we have to uncheck it.
$sel->click_ok("set_default_assignee");
$sel->uncheck_ok("set_default_assignee");
$sel->type_ok("comment", "-> Moving back to Testproduct.");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Verify New Product Details...");
$sel->select_ok("component", "label=TestComponent");
$sel->is_text_present_ok("These groups are not legal for the 'TestProduct' product or you are not allowed to restrict bugs to these groups");
$sel->is_element_present_ok('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]');
ok(!$sel->is_editable('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]'), "QA-Selenium-TEST group not editable");
ok(!$sel->is_checked('//input[@type="checkbox" and @name="groups" and @value="QA-Selenium-TEST"]'), "QA-Selenium-TEST group not selected");
$sel->is_element_present_ok('//input[@type="checkbox" and @name="groups" and @value="Master"]');
$sel->is_editable_ok('//input[@type="checkbox" and @name="groups" and @value="Master"]');
ok(!$sel->is_checked('//input[@type="checkbox" and @name="groups" and @value="Master"]'), "Master group not selected by default");
edit_bug($sel, $bug1_id, $bug_summary, {id => "change_product"});
logout($sel);
# The unprivileged user can view the bug again, but cannot
# edit it, except adding comments.
log_in($sel, $config, 'unprivileged');
go_to_bug($sel, $bug1_id);
$sel->type_ok("comment", "I have no privs, I can only comment (and remove people from the CC list)");
ok(!$sel->is_element_present('//select[@name="product"]'), "Product field not editable");
ok(!$sel->is_element_present('//select[@name="bug_severity"]'), "Severity field not editable");
ok(!$sel->is_element_present('//select[@name="priority"]'), "Priority field not editable");
ok(!$sel->is_element_present('//select[@name="op_sys"]'), "OS field not editable");
ok(!$sel->is_element_present('//select[@name="rep_platform"]'), "Hardware field not editable");
$sel->click_ok("cc_edit_area_showhide");
$sel->add_selection_ok("cc", "label=" . $config->{admin_user_login});
$sel->click_ok("removecc");
edit_bug($sel, $bug1_id, $bug_summary);
logout($sel);
# Now let's test the CANEDIT bit.
log_in($sel, $config, 'admin');
edit_product($sel, "TestProduct");
$sel->click_ok("link=Edit Group Access Controls:");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Group Controls for TestProduct");
$sel->check_ok("canedit_$master_gid");
$sel->click_ok("submit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Update group access controls for TestProduct");
# The user is in the master group, so he can comment.
go_to_bug($sel, $bug1_id);
$sel->type_ok("comment", "Do nothing except adding a comment...");
edit_bug($sel, $bug1_id, $bug_summary);
logout($sel);
# This user is not in the master group, so he cannot comment.
log_in($sel, $config, 'QA_Selenium_TEST');
go_to_bug($sel, $bug1_id);
$sel->type_ok("comment", "Just a comment too...");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Product Edit Access Denied");
$sel->is_text_present_ok("You are not permitted to edit bugs in product TestProduct.");
logout($sel);
# Test searches and "format for printing".
log_in($sel, $config, 'admin');
open_advanced_search_page($sel);
$sel->remove_all_selections_ok("product");
$sel->add_selection_ok("product", "TestProduct");
$sel->remove_all_selections_ok("bug_status");
$sel->remove_all_selections_ok("resolution");
$sel->check_ok("emailassigned_to1");
$sel->select_ok("emailtype1", "label=is");
$sel->type_ok("email1", $config->{admin_user_login});
$sel->check_ok("emailassigned_to2");
$sel->check_ok("emailqa_contact2");
$sel->check_ok("emailcc2");
$sel->select_ok("emailtype2", "label=is");
$sel->type_ok("email2", $config->{QA_Selenium_TEST_user_login});
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("One bug found.");
$sel->type_ok("save_newqueryname", "My bugs from QA_Selenium");
$sel->click_ok("remember");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search created");
$sel->is_text_present_ok("OK, you have a new search named My bugs from QA_Selenium.");
$sel->click_ok("link=My bugs from QA_Selenium");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: My bugs from QA_Selenium");
$sel->click_ok("long_format");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Full Text Bug Listing");
$sel->is_text_present_ok("Bug $bug1_id");
$sel->is_text_present_ok("Status: CONFIRMED");
$sel->is_text_present_ok("Reporter: QA-Selenium-TEST <$config->{QA_Selenium_TEST_user_login}>");
$sel->is_text_present_ok("Assignee: admin <$config->{admin_user_login}>");
$sel->is_text_present_ok("Severity: blocker");
$sel->is_text_present_ok("Priority: Highest");
$sel->is_text_present_ok("I have no privs, I can only comment");
logout($sel);
# Let's create a 2nd bug by this user so that we can test mass-change
# using the saved search the admin just created.
log_in($sel, $config, 'QA_Selenium_TEST');
file_bug_in_product($sel, 'TestProduct');
my $bug_summary2 = "New bug from me";
$sel->select_ok("bug_severity", "label=blocker");
$sel->type_ok("short_desc", $bug_summary2);
# We turned on the CANEDIT bit for TestProduct.
$sel->type_ok("comment", "I can enter a new bug, but not edit it, right?");
my $bug2_id = create_bug($sel, $bug_summary2);
# Clicking the "Back" button and resubmitting the form again should trigger a warning.
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Enter Bug: TestProduct");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Suspicious Action");
$sel->is_text_present_ok("no valid token for the create_bug action while processing the 'post_bug.cgi' script");
$sel->click_ok("confirm");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/\d+ \S $bug_summary2/, "Bug created");
$sel->type_ok("comment", "New comment not allowed");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Product Edit Access Denied");
$sel->is_text_present_ok("You are not permitted to edit bugs in product TestProduct.");
logout($sel);
# Reassign the newly created bug to the admin.
log_in($sel, $config, 'admin');
go_to_bug($sel, $bug2_id);
$sel->click_ok("bz_assignee_edit_action");
$sel->type_ok("assigned_to", $config->{admin_user_login});
$sel->type_ok("comment", "Taking!");
edit_bug($sel, $bug2_id, $bug_summary2);
# Test mass-change.
$sel->click_ok("link=My bugs from QA_Selenium");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: My bugs from QA_Selenium");
$sel->is_text_present_ok("2 bugs found");
$sel->click_ok("mass_change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->click_ok("check_all");
$sel->type_ok("comment", 'Mass change"');
$sel->select_ok("bug_status", "label=RESOLVED");
$sel->select_ok("resolution", "label=WORKSFORME");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugs processed");
$sel->click_ok("link=$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/$bug1_id /);
$sel->selected_label_is("resolution", "WORKSFORME");
$sel->select_ok("resolution", "label=INVALID");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->selected_label_is("resolution", "INVALID");
$sel->click_ok("link=History");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Changes made to bug $bug1_id");
$sel->is_text_present_ok("URL foo.cgi?action=bar");
$sel->is_text_present_ok("Severity critical blocker");
$sel->is_text_present_ok("Whiteboard [Selenium was here] [Selenium was here][admin too]");
$sel->is_text_present_ok("Product QA-Selenium-TEST TestProduct");
$sel->is_text_present_ok("Status CONFIRMED RESOLVED");
# Last step: move bugs to another DB, if the extension is enabled.
if ($config->{test_extensions}) {
set_parameters($sel, { "Bug Moving" => {"move-to-url" => {type => "text", value => 'http://www.foo.com/'},
"move-to-address" => {type => "text", value => 'import@foo.com'},
"movers" => {type => "text", value => $config->{admin_user_login}}
}
});
$sel->click_ok("link=My bugs from QA_Selenium");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: My bugs from QA_Selenium");
$sel->is_text_present_ok("2 bugs found");
$sel->click_ok("mass_change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->click_ok("check_all");
$sel->type_ok("comment", "-> moved");
$sel->click_ok('oldbugmove');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugs processed");
$sel->is_text_present_ok("Changes submitted for bug $bug1_id");
$sel->is_text_present_ok("Changes submitted for bug $bug2_id");
$sel->click_ok("link=$bug2_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug2_id/);
$sel->selected_label_is("resolution", "MOVED");
$sel->is_text_present_ok("Bug moved to http://www.foo.com/.");
# Disable bug moving again.
set_parameters($sel, { "Bug Moving" => {"movers" => {type => "text", value => ""}} });
}
# Make sure token checks are working correctly for single bug editing and mass change,
# first with no token, then with an invalid token.
foreach my $params (["no_token_single_bug", ""], ["invalid_token_single_bug", "&token=1"]) {
my ($comment, $token) = @$params;
$sel->open_ok("/$config->{bugzilla_installation}/process_bug.cgi?id=$bug1_id&comment=$comment$token",
undef, "Edit a single bug with " . ($token ? "an invalid" : "no") . " token");
$sel->title_is("Suspicious Action");
$sel->is_text_present_ok($token ? "an invalid token" : "web browser directly");
edit_bug_and_return($sel, $bug1_id, $bug_summary, {id => "confirm"});
$sel->is_text_present_ok($comment);
}
foreach my $params (["no_token_mass_change", ""], ["invalid_token_mass_change", "&token=1"]) {
my ($comment, $token) = @$params;
$sel->open_ok("/$config->{bugzilla_installation}/process_bug.cgi?id_$bug1_id=1&id_$bug2_id=1&comment=$comment$token",
undef, "Mass change with " . ($token ? "an invalid" : "no") . " token");
$sel->title_is("Suspicious Action");
$sel->is_text_present_ok("no valid token for the buglist_mass_change action");
$sel->click_ok("confirm");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugs processed");
foreach my $bug_id ($bug1_id, $bug2_id) {
$sel->click_ok("link=$bug_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug_id /);
$sel->is_text_present_ok($comment);
next if $bug_id == $bug2_id;
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugs processed");
}
}
# Now move these bugs out of our radar.
$sel->click_ok("link=My bugs from QA_Selenium");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: My bugs from QA_Selenium");
$sel->is_text_present_ok("2 bugs found");
$sel->click_ok("mass_change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->click_ok("check_all");
$sel->type_ok("comment", "Reassigning to the reporter");
$sel->type_ok("assigned_to", $config->{QA_Selenium_TEST_user_login});
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugs processed");
# Now delete the saved search.
$sel->click_ok("link=My bugs from QA_Selenium");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: My bugs from QA_Selenium");
$sel->click_ok("forget_search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search is gone");
$sel->is_text_present_ok("OK, the My bugs from QA_Selenium search is gone");
# Reset the CANEDIT bit. We want it to be turned off by default.
clear_canedit_on_testproduct($sel, $master_gid);
logout($sel);
sub clear_canedit_on_testproduct {
my ($sel, $master_gid) = @_;
edit_product($sel, "TestProduct");
$sel->click_ok("link=Edit Group Access Controls:");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Group Controls for TestProduct");
$sel->uncheck_ok("canedit_$master_gid");
$sel->click_ok("submit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Update group access controls for TestProduct");
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
set_parameters($sel, { "Bug Change Policies" => {"letsubmitterchoosepriority-off" => undef} });
file_bug_in_product($sel, "TestProduct");
ok(!$sel->is_text_present("Priority"), "The Priority label is not present");
ok(!$sel->is_element_present("//select[\@name='priority']"), "The Priority drop-down menu is not present");
set_parameters($sel, { "Bug Change Policies" => {"letsubmitterchoosepriority-on" => undef} });
file_bug_in_product($sel, "TestProduct");
$sel->is_text_present_ok("Priority");
$sel->is_element_present_ok("//select[\@name='priority']");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Enable classifications
log_in($sel, $config, 'admin');
set_parameters($sel, { "Bug Fields" => {"useclassification-on" => undef} });
# Create a new classification.
go_to_admin($sel);
$sel->click_ok("link=Classifications");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select classification");
# Delete old classifications if this script failed.
# Accessing action=delete directly must 1) trigger the security check page,
# and 2) automatically reclassify products in this classification.
if ($sel->is_text_present("cone")) {
$sel->open_ok("/$config->{bugzilla_installation}/editclassifications.cgi?action=delete&amp;classification=cone");
$sel->title_is("Suspicious Action");
$sel->click_ok("confirm");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Classification Deleted");
}
if ($sel->is_text_present("ctwo")) {
$sel->open_ok("/$config->{bugzilla_installation}/editclassifications.cgi?action=delete&amp;classification=ctwo");
$sel->title_is("Suspicious Action");
$sel->click_ok("confirm");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Classification Deleted");
}
$sel->click_ok("link=Add a new classification");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add new classification");
$sel->type_ok("classification", "cone");
$sel->type_ok("description", "Classification number 1");
$sel->click_ok('//input[@type="submit" and @value="Add"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("New Classification Created");
# Add TestProduct to the new classification. There should be no other
# products in this classification.
$sel->select_ok("prodlist", "value=TestProduct");
$sel->click_ok("add_products");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Reclassify products");
my @products = $sel->get_select_options("myprodlist");
ok(scalar @products == 1 && $products[0] eq 'TestProduct', "TestProduct successfully added to 'cone'");
# Create a new bug in this product/classification.
file_bug_in_product($sel, 'TestProduct', 'cone');
my $bug_summary = "Bug in classification cone";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "Created by Selenium with classifications turned on");
create_bug($sel, $bug_summary);
# Rename 'cone' to 'Unclassified', which must be rejected as it already exists,
# then to 'ctwo', which is not yet in use. Should work fine, even with products
# already in it.
go_to_admin($sel);
$sel->click_ok("link=Classifications");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select classification");
$sel->click_ok("link=cone");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit classification");
$sel->type_ok("classification", "Unclassified");
$sel->click_ok("//input[\@value='Update']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Classification Already Exists");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit classification");
$sel->type_ok("classification", "ctwo");
$sel->click_ok("//input[\@value='Update']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Classification Updated");
# Now try to delete the 'ctwo' classification. It should fail as there are
# products in it.
go_to_admin($sel);
$sel->click_ok("link=Classifications");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select classification");
$sel->click_ok('//a[@href="editclassifications.cgi?action=del&classification=ctwo"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Error");
my $error = trim($sel->get_text("error_msg"));
ok($error =~ /there are products for this classification/, "Reject classification deletion");
# Reclassify the product before deleting the classification.
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select classification");
$sel->click_ok('//a[@href="editclassifications.cgi?action=reclassify&classification=ctwo"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Reclassify products");
$sel->add_selection_ok("myprodlist", "label=TestProduct");
$sel->click_ok("remove_products");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Reclassify products");
$sel->click_ok("link=edit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select classification");
$sel->click_ok('//a[@href="editclassifications.cgi?action=del&classification=ctwo"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete classification");
$sel->is_text_present_ok("Do you really want to delete this classification?");
$sel->click_ok("//input[\@value='Yes, delete']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Classification Deleted");
# Disable classifications and make sure you cannot edit them anymore.
set_parameters($sel, { "Bug Fields" => {"useclassification-off" => undef} });
$sel->open_ok("/$config->{bugzilla_installation}/editclassifications.cgi");
$sel->title_is("Classification Not Enabled");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Turn on 'requirelogin' and log out.
log_in($sel, $config, 'admin');
set_parameters($sel, { "User Authentication" => {"requirelogin-on" => undef} });
logout($sel);
# Accessing config.cgi should display no sensitive data.
$sel->open_ok("/$config->{bugzilla_installation}/config.cgi", undef, "Go to config.cgi (JS format)");
$sel->is_text_present_ok("var status = [ ];");
$sel->is_text_present_ok("var status_open = [ ];");
$sel->is_text_present_ok("var status_closed = [ ];");
$sel->is_text_present_ok("var resolution = [ ];");
$sel->is_text_present_ok("var keyword = [ ];");
$sel->is_text_present_ok("var platform = [ ];");
$sel->is_text_present_ok("var severity = [ ];");
$sel->is_text_present_ok("var field = [\n];");
ok(!$sel->is_text_present("cf_"), "No custom field displayed");
ok(!$sel->is_text_present("component["), "No component displayed");
ok(!$sel->is_text_present("version["), "No version displayed");
ok(!$sel->is_text_present("target_milestone["), "No target milestone displayed");
# Turn on 'requirelogin' and log out.
log_in($sel, $config, 'admin');
set_parameters($sel, { "User Authentication" => {"requirelogin-off" => undef} });
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Set the email regexp for new bugzilla accounts to end with @bugzilla.test.
log_in($sel, $config, 'admin');
set_parameters($sel, { "User Authentication" => {"createemailregexp" => {type => "text", value => '[^@]+@bugzilla\.test$'}} });
logout($sel);
# Create a valid account. We need to randomize the login address, because a request
# expires after 3 days only and this test can be executed several times per day.
my $valid_account = 'selenium-' . random_string(10) . '@bugzilla.test';
$sel->click_ok("link=Home");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugzilla Main Page");
$sel->is_text_present_ok("Open a New Account");
$sel->click_ok("link=Open a New Account");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create a new Bugzilla account");
$sel->type_ok("login", $valid_account);
$sel->click_ok("send");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Request for new user account '$valid_account' submitted");
$sel->is_text_present_ok("A confirmation email has been sent");
# Try creating the same account again. It's too soon.
$sel->click_ok("link=Home");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugzilla Main Page");
$sel->is_text_present_ok("Open a New Account");
$sel->click_ok("link=Open a New Account");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create a new Bugzilla account");
$sel->type_ok("login", $valid_account);
$sel->click_ok("send");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Too Soon For New Token");
my $error_msg = trim($sel->get_text("error_msg"));
ok($error_msg =~ /Please wait 10 minutes/, "Too soon for this account");
# These accounts do not pass the regexp.
my @accounts = ('test@yahoo.com', 'test@bugzilla.net', 'test@bugzilla.test.com');
foreach my $account (@accounts) {
$sel->click_ok("link=New Account");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create a new Bugzilla account");
$sel->type_ok("login", $account);
$sel->click_ok("send");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Account Creation Restricted");
$sel->is_text_present_ok("User account creation has been restricted.");
}
# These accounts are illegal.
@accounts = ('test\bugzilla@bugzilla.test', 'test@bugzilla.org@bugzilla.test', 'test@bugzilla..test');
# Logins larger than 127 characters must be rejected, for security reasons.
push @accounts, 'selenium-' . random_string(110) . '@bugzilla.test';
foreach my $account (@accounts) {
$sel->click_ok("link=New Account");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create a new Bugzilla account");
# Starting with 5.0, the login field is a type=email and is marked "required"
# This means that we need to add the novalidate attribute to the enclosing form
# so that the illegal login can still be checked by the backend code.
my $script = q{
document.getElementById('account_creation_form').setAttribute('novalidate', 1);
};
$sel->run_script($script);
$sel->type_ok("login", $account);
$sel->click_ok("send");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Invalid Email Address");
my $error_msg = trim($sel->get_text("error_msg"));
ok($error_msg =~ /^The e-mail address you entered (\S+) didn't pass our syntax checking/, "Invalid email address detected");
}
# This account already exists.
$sel->click_ok("link=New Account");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create a new Bugzilla account");
$sel->type_ok("login", $config->{admin_user_login});
$sel->click_ok("send");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Account Already Exists");
$error_msg = trim($sel->get_text("error_msg"));
ok($error_msg eq "There is already an account with the login name $config->{admin_user_login}.", "Account already exists");
# Turn off user account creation.
log_in($sel, $config, 'admin');
set_parameters($sel, { "User Authentication" => {"createemailregexp" => {type => "text", value => ''}} });
logout($sel);
# Make sure that links pointing to createaccount.cgi are all deactivated.
ok(!$sel->is_text_present("New Account"), "No link named 'New Account'");
$sel->click_ok("link=Home");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugzilla Main Page");
ok(!$sel->is_text_present("Open a New Account"), "No link named 'Open a New Account'");
$sel->open_ok("/$config->{bugzilla_installation}/createaccount.cgi");
$sel->title_is("Account Creation Disabled");
$error_msg = trim($sel->get_text("error_msg"));
ok($error_msg =~ /^User account creation has been disabled. New accounts must be created by an administrator/,
"User account creation disabled");
# Re-enable user account creation.
log_in($sel, $config, 'admin');
set_parameters($sel, { "User Authentication" => {"createemailregexp" => {type => "text", value => '.*'}} });
# Make sure selenium-<random_string>@bugzilla.test has not be added to the DB yet.
go_to_admin($sel);
$sel->click_ok("link=Users");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search users");
$sel->type_ok("matchstr", $valid_account);
$sel->click_ok("search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select user");
$sel->is_text_present_ok("0 users found");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
# Create new bug to test custom fields
file_bug_in_product($sel, 'TestProduct');
my $bug_summary = "What's your ID?";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "Use the ID of this bug to generate a unique custom field name.");
$sel->type_ok("bug_severity", "label=normal");
my $bug1_id = create_bug($sel, $bug_summary);
# Create custom fields
go_to_admin($sel);
$sel->click_ok("link=Custom Fields");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Fields");
$sel->click_ok("link=Add a new custom field");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add a new Custom Field");
$sel->type_ok("name", "cf_qa_freetext_$bug1_id");
$sel->type_ok("desc", "Freetext$bug1_id");
$sel->select_ok("type", "label=Free Text");
$sel->type_ok("sortkey", $bug1_id);
# These values are off by default.
$sel->value_is("enter_bug", "off");
$sel->value_is("obsolete", "off");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Field Created");
$sel->is_text_present_ok("The new custom field 'cf_qa_freetext_$bug1_id' has been successfully created.");
$sel->click_ok("link=Add a new custom field");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add a new Custom Field");
$sel->type_ok("name", "cf_qa_list_$bug1_id");
$sel->type_ok("desc", "List$bug1_id");
$sel->select_ok("type", "label=Drop Down");
$sel->type_ok("sortkey", $bug1_id);
$sel->click_ok("enter_bug");
$sel->value_is("enter_bug", "on");
$sel->click_ok("new_bugmail");
$sel->value_is("new_bugmail", "on");
$sel->value_is("obsolete", "off");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Field Created");
$sel->is_text_present_ok("The new custom field 'cf_qa_list_$bug1_id' has been successfully created.");
$sel->click_ok("link=Add a new custom field");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add a new Custom Field");
$sel->type_ok("name", "cf_qa_bugid_$bug1_id");
$sel->type_ok("desc", "Reference$bug1_id");
$sel->select_ok("type", "label=Bug ID");
$sel->type_ok("sortkey", $bug1_id);
$sel->type_ok("reverse_desc", "IsRef$bug1_id");
$sel->click_ok("enter_bug");
$sel->value_is("enter_bug", "on");
$sel->value_is("obsolete", "off");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Field Created");
$sel->is_text_present_ok("The new custom field 'cf_qa_bugid_$bug1_id' has been successfully created.");
# Add values to the custom fields.
$sel->click_ok("link=cf_qa_list_$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit the Custom Field 'cf_qa_list_$bug1_id' (List$bug1_id)");
$sel->click_ok("link=Edit legal values for this field");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select value for the 'List$bug1_id' (cf_qa_list_$bug1_id) field");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add Value for the 'List$bug1_id' (cf_qa_list_$bug1_id) field");
$sel->type_ok("value", "have fun?");
$sel->type_ok("sortkey", "805");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("New Field Value Created");
$sel->is_text_present_ok("The value have fun? has been added as a valid choice for the List$bug1_id (cf_qa_list_$bug1_id) field.");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add Value for the 'List$bug1_id' (cf_qa_list_$bug1_id) field");
$sel->type_ok("value", "storage");
$sel->type_ok("sortkey", "49");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("New Field Value Created");
$sel->is_text_present_ok("The value storage has been added as a valid choice for the List$bug1_id (cf_qa_list_$bug1_id) field.");
# Also create a new bug status and a new resolution.
go_to_admin($sel);
$sel->click_ok("link=Field Values");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit values for which field?");
$sel->click_ok("link=Resolution");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select value for the 'Resolution' (resolution) field");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add Value for the 'Resolution' (resolution) field");
$sel->type_ok("value", "UPSTREAM");
$sel->type_ok("sortkey", 450);
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("New Field Value Created");
go_to_admin($sel);
$sel->click_ok("link=Field Values");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit values for which field?");
$sel->click_ok("link=Status");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select value for the 'Status' (bug_status) field");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add Value for the 'Status' (bug_status) field");
$sel->type_ok("value", "SUSPENDED");
$sel->type_ok("sortkey", 250);
$sel->click_ok("open_status");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("New Field Value Created");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add Value for the 'Status' (bug_status) field");
$sel->type_ok("value", "IN_QA");
$sel->type_ok("sortkey", 550);
$sel->click_ok("closed_status");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("New Field Value Created");
$sel->click_ok("link=status workflow page");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Workflow");
$sel->click_ok('//td[@title="From UNCONFIRMED to SUSPENDED"]//input[@type="checkbox"]');
$sel->click_ok('//td[@title="From CONFIRMED to SUSPENDED"]//input[@type="checkbox"]');
$sel->click_ok('//td[@title="From SUSPENDED to CONFIRMED"]//input[@type="checkbox"]');
$sel->click_ok('//td[@title="From SUSPENDED to IN_PROGRESS"]//input[@type="checkbox"]');
$sel->click_ok('//td[@title="From RESOLVED to IN_QA"]//input[@type="checkbox"]');
$sel->click_ok('//td[@title="From IN_QA to VERIFIED"]//input[@type="checkbox"]');
$sel->click_ok('//td[@title="From IN_QA to CONFIRMED"]//input[@type="checkbox"]');
$sel->click_ok('//input[@value="Commit Changes"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Workflow");
# Create new bug to test custom fields in bug creation page
file_bug_in_product($sel, 'TestProduct');
$sel->is_text_present_ok("List$bug1_id:");
$sel->is_element_present_ok("cf_qa_list_$bug1_id");
$sel->is_text_present_ok("Reference$bug1_id:");
$sel->is_element_present_ok("cf_qa_bugid_$bug1_id");
ok(!$sel->is_text_present("Freetext$bug1_id:"), "Freetext$bug1_id is not displayed");
ok(!$sel->is_element_present("cf_qa_freetext_$bug1_id"), "cf_qa_freetext_$bug1_id is not available");
my $bug_summary2 = "Et de un";
$sel->type_ok("short_desc", $bug_summary2);
$sel->select_ok("bug_severity", "critical");
$sel->type_ok("cf_qa_bugid_$bug1_id", $bug1_id);
my $bug2_id = create_bug($sel, $bug_summary2);
# Both fields are editable.
$sel->type_ok("cf_qa_freetext_$bug1_id", "bonsai");
$sel->selected_label_is("cf_qa_list_$bug1_id", "---");
$sel->select_ok("bug_status", "label=SUSPENDED");
edit_bug($sel, $bug2_id, $bug_summary2);
go_to_bug($sel, $bug1_id);
$sel->type_ok("cf_qa_freetext_$bug1_id", "dumbo");
$sel->select_ok("cf_qa_list_$bug1_id", "label=storage");
$sel->is_text_present_ok("IsRef$bug1_id: $bug2_id");
$sel->select_ok("bug_status", "RESOLVED");
$sel->select_ok("resolution", "UPSTREAM");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->select_ok("bug_status", "IN_QA");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->click_ok("link=Format For Printing");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Full Text Bug Listing");
$sel->is_text_present_ok("Freetext$bug1_id: dumbo");
$sel->is_text_present_ok("List$bug1_id: storage");
$sel->is_text_present_ok("Status: IN_QA UPSTREAM");
go_to_bug($sel, $bug2_id);
$sel->select_ok("cf_qa_list_$bug1_id", "label=storage");
edit_bug($sel, $bug2_id, $bug_summary2);
# Test searching for bugs using the custom fields
open_advanced_search_page($sel);
$sel->remove_all_selections_ok("product");
$sel->add_selection_ok("product", "TestProduct");
$sel->remove_all_selections("bug_status");
$sel->remove_all_selections("resolution");
$sel->select_ok("f1", "label=List$bug1_id");
$sel->select_ok("o1", "label=is equal to");
$sel->type_ok("v1", "storage");
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("2 bugs found");
$sel->is_text_present_ok("What's your ID?");
$sel->is_text_present_ok("Et de un");
# Now edit custom fields in mass changes.
$sel->click_ok("mass_change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->click_ok("check_all");
$sel->select_ok("cf_qa_list_$bug1_id", "label=---");
$sel->type_ok("cf_qa_freetext_$bug1_id", "thanks");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugs processed");
$sel->click_ok("link=$bug2_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug2_id/);
$sel->value_is("cf_qa_freetext_$bug1_id", "thanks");
$sel->selected_label_is("cf_qa_list_$bug1_id", "---");
$sel->select_ok("cf_qa_list_$bug1_id", "label=storage");
edit_bug($sel, $bug2_id, $bug_summary2);
# Let's now test custom field visibility.
go_to_admin($sel);
$sel->click_ok("link=Custom Fields");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Fields");
$sel->click_ok("link=cf_qa_list_$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit the Custom Field 'cf_qa_list_$bug1_id' (List$bug1_id)");
$sel->select_ok("visibility_field_id", "label=Severity (bug_severity)");
$sel->add_selection_ok("visibility_values", "label=blocker");
$sel->add_selection_ok("visibility_values", "label=critical");
$sel->click_ok("edit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Field Updated");
go_to_bug($sel, $bug1_id);
$sel->is_element_present_ok("cf_qa_list_$bug1_id", "List$bug1_id is in the DOM of the page...");
ok(!$sel->is_visible("cf_qa_list_$bug1_id"), "... but is not displayed with severity = 'normal'");
$sel->select_ok("bug_severity", "major");
ok(!$sel->is_visible("cf_qa_list_$bug1_id"), "... nor with severity = 'major'");
$sel->select_ok("bug_severity", "critical");
$sel->is_visible_ok("cf_qa_list_$bug1_id", "... but is visible with severity = 'critical'");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->is_visible_ok("cf_qa_list_$bug1_id");
go_to_bug($sel, $bug2_id);
$sel->is_visible_ok("cf_qa_list_$bug1_id");
$sel->select_ok("bug_severity", "minor");
ok(!$sel->is_visible("cf_qa_list_$bug1_id"), "List$bug1_id is not displayed with severity = 'minor'");
edit_bug_and_return($sel, $bug2_id, $bug_summary2);
ok(!$sel->is_visible("cf_qa_list_$bug1_id"), "List$bug1_id is not displayed with severity = 'minor'");
# Add a new value which is only listed under some condition.
go_to_admin($sel);
$sel->click_ok("link=Custom Fields");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Fields");
$sel->click_ok("link=cf_qa_list_$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit the Custom Field 'cf_qa_list_$bug1_id' (List$bug1_id)");
$sel->select_ok("value_field_id", "label=Resolution (resolution)");
$sel->click_ok("edit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Field Updated");
$sel->click_ok("link=cf_qa_list_$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit the Custom Field 'cf_qa_list_$bug1_id' (List$bug1_id)");
$sel->click_ok("link=Edit legal values for this field");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select value for the 'List$bug1_id' (cf_qa_list_$bug1_id) field");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add Value for the 'List$bug1_id' (cf_qa_list_$bug1_id) field");
$sel->type_ok("value", "ghost");
$sel->type_ok("sortkey", "500");
$sel->select_ok("visibility_value_id", "label=FIXED");
$sel->click_ok("id=create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("New Field Value Created");
go_to_bug($sel, $bug1_id);
my @labels = $sel->get_select_options("cf_qa_list_$bug1_id");
ok(grep(/^ghost$/, @labels), "ghost is in the DOM of the page...");
my $disabled = $sel->get_attribute("v4_cf_qa_list_$bug1_id\@disabled");
ok($disabled, "... but is not available for selection by default");
$sel->select_ok("bug_status", "label=RESOLVED");
$sel->select_ok("resolution", "label=FIXED");
$sel->select_ok("cf_qa_list_$bug1_id", "label=ghost");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->selected_label_is("cf_qa_list_$bug1_id", "ghost");
# Delete an unused field value.
go_to_admin($sel);
$sel->click_ok("link=Field Values");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit values for which field?");
$sel->click_ok("link=List$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select value for the 'List$bug1_id' (cf_qa_list_$bug1_id) field");
$sel->click_ok("//a[contains(\@href, 'editvalues.cgi?action=del&field=cf_qa_list_$bug1_id&value=have%20fun%3F')]");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Value 'have fun?' from the 'List$bug1_id' (cf_qa_list_$bug1_id) field");
$sel->is_text_present_ok("Do you really want to delete this value?");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Field Value Deleted");
# This value cannot be deleted as it's in use.
$sel->click_ok("//a[contains(\@href, 'editvalues.cgi?action=del&field=cf_qa_list_$bug1_id&value=storage')]");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Value 'storage' from the 'List$bug1_id' (cf_qa_list_$bug1_id) field");
$sel->is_text_present_ok("There is 1 bug with this field value");
# Mark the <select> field as obsolete, making it unavailable in bug reports.
go_to_admin($sel);
$sel->click_ok("link=Custom Fields");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Fields");
$sel->click_ok("link=cf_qa_list_$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit the Custom Field 'cf_qa_list_$bug1_id' (List$bug1_id)");
$sel->click_ok("obsolete");
$sel->value_is("obsolete", "on");
$sel->click_ok("edit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Field Updated");
go_to_bug($sel, $bug1_id);
$sel->value_is("cf_qa_freetext_$bug1_id", "thanks");
ok(!$sel->is_element_present("cf_qa_list_$bug1_id"), "The custom list is not visible");
# Custom fields are also viewable by logged out users.
logout($sel);
go_to_bug($sel, $bug1_id);
$sel->is_text_present_ok("Freetext$bug1_id: thanks");
# Powerless users should still be able to CC themselves when
# custom fields are in use.
log_in($sel, $config, 'unprivileged');
go_to_bug($sel, $bug1_id);
$sel->is_text_present_ok("Freetext$bug1_id: thanks");
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", $config->{unprivileged_user_login});
edit_bug($sel, $bug1_id, $bug_summary);
logout($sel);
# Disable the remaining free text field.
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Custom Fields");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Fields");
$sel->click_ok("link=cf_qa_freetext_$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit the Custom Field 'cf_qa_freetext_$bug1_id' (Freetext$bug1_id)");
$sel->click_ok("obsolete");
$sel->value_is("obsolete", "on");
$sel->click_ok("edit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Field Updated");
# Trying to delete a bug status which is in use is forbidden.
go_to_admin($sel);
$sel->click_ok("link=Field Values");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit values for which field?");
$sel->click_ok("link=Status");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select value for the 'Status' (bug_status) field");
$sel->click_ok('//a[@href="editvalues.cgi?action=del&field=bug_status&value=SUSPENDED"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Value 'SUSPENDED' from the 'Status' (bug_status) field");
$sel->is_text_present_ok("Sorry, but the 'SUSPENDED' value cannot be deleted");
go_to_bug($sel, $bug2_id);
$sel->select_ok("bug_status", "CONFIRMED");
edit_bug($sel, $bug2_id, $bug_summary2);
go_to_bug($sel, $bug1_id);
$sel->select_ok("bug_status", "VERIFIED");
$sel->select_ok("resolution", "INVALID");
edit_bug($sel, $bug1_id, $bug_summary);
# Unused values can be deleted.
go_to_admin($sel);
$sel->click_ok("link=Field Values");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit values for which field?");
$sel->click_ok("link=Status");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select value for the 'Status' (bug_status) field");
$sel->click_ok('//a[@href="editvalues.cgi?action=del&field=bug_status&value=SUSPENDED"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Value 'SUSPENDED' from the 'Status' (bug_status) field");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Field Value Deleted");
$sel->is_text_present_ok("The value SUSPENDED of the Status (bug_status) field has been deleted");
$sel->click_ok('//a[@href="editvalues.cgi?action=del&field=bug_status&value=IN_QA"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Value 'IN_QA' from the 'Status' (bug_status) field");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Field Value Deleted");
$sel->is_text_present_ok("The value IN_QA of the Status (bug_status) field has been deleted");
go_to_admin($sel);
$sel->click_ok("link=Field Values");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit values for which field?");
$sel->click_ok("link=Resolution");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select value for the 'Resolution' (resolution) field");
$sel->click_ok('//a[@href="editvalues.cgi?action=del&field=resolution&value=UPSTREAM"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Value 'UPSTREAM' from the 'Resolution' (resolution) field");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Field Value Deleted");
$sel->is_text_present_ok("The value UPSTREAM of the Resolution (resolution) field has been deleted");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
# Create a custom field, going through each type available,
# mark it as obsolete and delete it immediately.
go_to_admin($sel);
$sel->click_ok("link=Custom Fields");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Fields");
my @types = ("Bug ID", "Large Text Box", "Free Text", "Multiple-Selection Box",
"Drop Down", "Date/Time");
my $counter = int(rand(10000));
foreach my $type (@types) {
my $fname = "cf_field" . ++$counter;
my $fdesc = "Field" . $counter;
$sel->click_ok("link=Add a new custom field");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add a new Custom Field");
$sel->type_ok("name", $fname);
$sel->type_ok("desc", $fdesc);
$sel->select_ok("type", "label=$type");
$sel->click_ok("obsolete");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Field Created");
$sel->click_ok("//a[\@href='editfields.cgi?action=del&name=$fname']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete the Custom Field '$fname' ($fdesc)");
$sel->click_ok("link=Delete field '$fdesc'");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Custom Field Deleted");
}
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Let's create a public and a private bug.
log_in($sel, $config, 'admin');
file_bug_in_product($sel, "TestProduct");
my $bug_summary = "Dependency Checks";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "This bug is public");
my $bug1_id = create_bug($sel, $bug_summary);
file_bug_in_product($sel, "TestProduct");
$sel->type_ok("alias", "secret_qa_bug_$bug1_id+1");
my $bug_summary2 = "Big Ben";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", "This bug is private");
$sel->type_ok("dependson", $bug1_id);
$sel->check_ok('//input[@name="groups" and @value="Master"]');
my $bug2_id = create_bug($sel, $bug_summary2);
go_to_bug($sel, $bug1_id);
$sel->click_ok("link=Mark as Duplicate");
$sel->type_ok("dup_id", $bug2_id);
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->is_text_present_ok("secret_qa_bug_$bug1_id+1");
logout($sel);
# A user with editbugs privs who cannot see some bugs in the dependency list
# or the bug this duplicate points to should still be able to edit this bug.
log_in($sel, $config, 'editbugs');
go_to_bug($sel, $bug1_id);
ok(!$sel->is_text_present("secret_qa_bug_$bug1_id+1"), "The alias of the private bug is not visible");
$sel->select_ok("priority", "label=High");
$sel->select_ok("bug_status", "VERIFIED");
$sel->type_ok("comment", "Can I still edit this bug?");
edit_bug($sel, $bug1_id, $bug_summary);
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
my $admin_user_login = $config->{admin_user_login};
my $unprivileged_user_login = $config->{unprivileged_user_login};
my $permanent_user = $config->{permanent_user};
log_in($sel, $config, 'admin');
set_parameters($sel, { "Bug Fields" => {"useclassification-off" => undef,
"usetargetmilestone-on" => undef},
"Administrative Policies" => {"allowbugdeletion-on" => undef}
});
# Create a product and add components to it. Do some cleanup first
# if the script failed during a previous run.
go_to_admin($sel);
$sel->click_ok("link=Products");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
# No risk to get the "Select classification" page. We turned off useclassification.
$sel->title_is("Select product");
my $text = trim($sel->get_text("bugzilla-body"));
if ($text =~ /(Kill me!|Kill me nicely)/) {
my $product = $1;
my $escaped_product = url_quote($product);
$sel->click_ok("//a[\@href='editproducts.cgi?action=del&product=$escaped_product']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Product '$product'");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Product Deleted");
}
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add Product");
$sel->type_ok("product", "Kill me!");
$sel->type_ok("description", "I will disappear very soon. Do not add bugs to it.");
$sel->type_ok("defaultmilestone", "0.1a");
# Since Bugzilla 4.0, the voting system is in an extension.
if ($config->{test_extensions}) {
$sel->type_ok("votesperuser", "1");
$sel->type_ok("maxvotesperbug", "1");
$sel->type_ok("votestoconfirm", "10");
}
$sel->type_ok("version", "0.1a");
$sel->type_ok("component", "first comp");
$sel->type_ok("comp_desc", "comp 1");
$sel->type_ok("initialowner", $admin_user_login);
$sel->click_ok("add-product");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Product Created");
# Try creating a second component with the same name.
$sel->click_ok("link=Edit components:");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select component of product 'Kill me!'");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add component to the Kill me! product");
$sel->type_ok("component", "first comp");
$sel->type_ok("description", "comp 2");
$sel->type_ok("initialowner", $admin_user_login);
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Component Already Exists");
# Now really create a second component, with a distinct name.
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->type_ok("component", "second comp");
# FIXME - Re-enter the default assignee (regression due to bug 577574)
$sel->type_ok("initialowner", $admin_user_login);
$sel->type_ok("initialcc", $permanent_user);
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Component Created");
# Add a new version.
edit_product($sel, "Kill me!");
$sel->click_ok("link=Edit versions:");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select version of product 'Kill me!'");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->type_ok("version", "0.1");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Version Created");
# Add a new milestone.
$sel->click_ok("link='Kill me!'");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Product 'Kill me!'");
$sel->click_ok("link=Edit milestones:");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select milestone of product 'Kill me!'");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add Milestone to Product 'Kill me!'");
$sel->type_ok("milestone", "0.2");
$sel->type_ok("sortkey", "2");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Milestone Created");
# Add another milestone.
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add Milestone to Product 'Kill me!'");
$sel->type_ok("milestone", "0.1a");
# Negative sortkeys are valid for milestones.
$sel->type_ok("sortkey", "-2");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Milestone Already Exists");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->type_ok("milestone", "pre-0.1");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Milestone Created");
# Now create an UNCONFIRMED bug and add it to the newly created product.
file_bug_in_product($sel, "Kill me!");
$sel->select_ok("version", "label=0.1a");
$sel->select_ok("component", "label=first comp");
# UNCONFIRMED must be present.
$sel->select_ok("bug_status", "label=UNCONFIRMED");
$sel->type_ok("cc", $unprivileged_user_login);
$sel->type_ok("bug_file_loc", "http://www.test.com");
my $bug_summary = "test create/edit product properties";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "this bug will soon be dead");
my $bug1_id = create_bug($sel, $bug_summary);
my @cc_list = $sel->get_select_options("cc");
ok(grep($_ eq $unprivileged_user_login, @cc_list), "$unprivileged_user_login correctly added to the CC list");
ok(!grep($_ eq $permanent_user, @cc_list), "$permanent_user not in the CC list for 'first comp' by default");
# File a second bug, and make sure users in the default CC list are added.
file_bug_in_product($sel, "Kill me!");
$sel->select_ok("version", "label=0.1a");
$sel->select_ok("component", "label=second comp");
my $bug_summary2 = "check default CC list";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", "is the CC list populated correctly?");
create_bug($sel, $bug_summary2);
@cc_list = $sel->get_select_options("cc");
ok(grep($_ eq $permanent_user, @cc_list), "$permanent_user in the CC list for 'second comp' by default");
# Edit product properties and set votes_to_confirm to 0, which has
# the side-effect to disable auto-confirmation (new behavior compared
# to Bugzilla 3.4 and older).
edit_product($sel, "Kill me!");
$sel->type_ok("product", "Kill me nicely");
$sel->type_ok("description", "I will disappear very soon. Do not add bugs to it (except for testing).");
$sel->select_ok("defaultmilestone", "label=0.2");
if ($config->{test_extensions}) {
$sel->type_ok("votesperuser", "2");
$sel->type_ok("maxvotesperbug", 5);
$sel->type_ok("votestoconfirm", "0");
}
$sel->click_ok("update-product");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Updating Product 'Kill me nicely'");
$sel->is_text_present_ok("Updated product name from 'Kill me!' to 'Kill me nicely'");
$sel->is_text_present_ok("Updated description");
$sel->is_text_present_ok("Updated default milestone");
if ($config->{test_extensions}) {
$sel->is_text_present_ok("Updated votes per user");
$sel->is_text_present_ok("Updated maximum votes per bug");
$sel->is_text_present_ok("Updated number of votes needed to confirm a bug");
$text = trim($sel->get_text("bugzilla-body"));
# We use .{1} in place of the right arrow character, which fails otherwise.
ok($text =~ /Checking unconfirmed bugs in this product for any which now have sufficient votes\.{3} .{1}there were none/,
"No bugs confirmed by popular votes (votestoconfirm = 0 disables auto-confirmation)");
# Now set votestoconfirm to 2, vote for a bug, and then set
# this attribute back to 1, to trigger auto-confirmation.
$sel->click_ok("link=Kill me nicely");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Product 'Kill me nicely'", "Display properties of Kill me nicely");
$sel->type_ok("votestoconfirm", 2);
$sel->click_ok("update-product");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Updating Product 'Kill me nicely'");
$sel->is_text_present_ok("Updated number of votes needed to confirm a bug");
go_to_bug($sel, $bug1_id);
$sel->click_ok("link=vote");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Votes");
$sel->type_ok("bug_$bug1_id", 1);
$sel->click_ok("change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Votes");
$sel->is_text_present_ok("The changes to your votes have been saved");
edit_product($sel, "Kill me nicely");
$sel->type_ok("votestoconfirm", 1);
$sel->click_ok("update-product");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Updating Product 'Kill me nicely'");
$sel->is_text_present_ok("Updated number of votes needed to confirm a bug");
$text = trim($sel->get_text("bugzilla-body"));
ok($text =~ /Bug $bug1_id confirmed by number of votes/, "Bug $bug1_id is confirmed by popular votes");
}
# Edit the bug.
go_to_bug($sel, $bug1_id);
$sel->selected_label_is("product", "Kill me nicely");
$sel->selected_label_is("bug_status", "CONFIRMED") if $config->{test_extensions};
$sel->select_ok("target_milestone", "label=pre-0.1");
$sel->select_ok("component", "label=second comp");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
@cc_list = $sel->get_select_options("cc");
ok(grep($_ eq $permanent_user, @cc_list), "User $permanent_user automatically added to the CC list");
# Delete the milestone the bug belongs to. This should retarget the bug
# to the default milestone.
edit_product($sel, "Kill me nicely");
$sel->click_ok("link=Edit milestones:");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select milestone of product 'Kill me nicely'");
$sel->click_ok('//a[@href="editmilestones.cgi?action=del&product=Kill%20me%20nicely&milestone=pre-0.1"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Milestone of Product 'Kill me nicely'");
$text = trim($sel->get_text("bugzilla-body"));
ok($text =~ /There is 1 bug entered for this milestone/, "Warning displayed");
ok($text =~ /Do you really want to delete this milestone\?/, "Requesting confirmation");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Milestone Deleted");
$text = trim($sel->get_text("message"));
ok($text =~ /Bugs targetted to this milestone have been retargetted to the default milestone/, "Bug retargetted");
# Try deleting the version used by the bug. This action must be rejected.
$sel->click_ok("link='Kill me nicely'");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Product 'Kill me nicely'");
$sel->click_ok("link=Edit versions:");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select version of product 'Kill me nicely'");
$sel->click_ok("//a[contains(\@href, 'editversions.cgi?action=del&product=Kill%20me%20nicely&version=0.1a')]");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Version of Product 'Kill me nicely'");
$text = trim($sel->get_text("bugzilla-body"));
ok($text =~ /Sorry, there are 2 outstanding bugs for this version/, "Rejecting version deletion");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
# Delete an unused version. The action must succeed.
$sel->click_ok('//a[@href="editversions.cgi?action=del&product=Kill%20me%20nicely&version=0.1"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Version of Product 'Kill me nicely'");
$text = trim($sel->get_text("bugzilla-body"));
ok($text =~ /Do you really want to delete this version\?/, "Requesting confirmation");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Version Deleted");
# Delete the component the bug belongs to. The action must succeed.
$sel->click_ok("link='Kill me nicely'");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Product 'Kill me nicely'");
$sel->click_ok("link=Edit components:");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select component of product 'Kill me nicely'");
$sel->click_ok("//a[contains(\@href, 'editcomponents.cgi?action=del&product=Kill%20me%20nicely&component=second%20comp')]");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete component 'second comp' from 'Kill me nicely' product");
$text = trim($sel->get_text("bugzilla-body"));
ok($text =~ /There are 2 bugs entered for this component/, "Warning displayed");
ok($text =~ /Do you really want to delete this component\?/, "Requesting confirmation");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Component Deleted");
$text = trim($sel->get_text("bugzilla-body"));
ok($text =~ /The component second comp has been deleted/, "Component deletion confirmed");
ok($text =~ /All bugs being in this component and all references to them have also been deleted/,
"Bug deletion confirmed");
# Only one value for component, version and milestone available. They should
# be selected by default.
file_bug_in_product($sel, "Kill me nicely");
$bug_summary2 = "bye bye everybody!";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", "I'm dead :(");
create_bug($sel, $bug_summary2);
# Now delete the product.
go_to_admin($sel);
$sel->click_ok("link=Products");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select product");
$sel->click_ok("//a[\@href='editproducts.cgi?action=del&product=Kill%20me%20nicely']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Product 'Kill me nicely'");
$text = trim($sel->get_text("bugzilla-body"));
ok($text =~ /There is 1 bug entered for this product/, "Warning displayed");
ok($text =~ /Do you really want to delete this product\?/, "Confirmation request displayed");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Product Deleted");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Used to test sent bugmails
use constant RCPT_BOTH => 1;
use constant RCPT_ADMIN => 2;
use constant RCPT_NORMAL => 3;
use constant RCPT_NONE => 4;
my @email_both = ($config->{admin_user_login}, $config->{editbugs_user_login});
my @email_admin = ($config->{admin_user_login});
my @email_normal = ($config->{editbugs_user_login});
my @email_none = ("no one");
# Test script to test email preferences.
# For reference, following bugmail and request mails should be generated.
#
# Admin should get following bugmails (in order):
# 1) A bug is created
# 2) Normal user adds a CC for itself
# 3) Admin removes CC of normal user
# 4) Admin assigns the bug to itself
# 5) Admin requests a flag from normal user
# 6) Admin grants a flag requested from itself
# 7) Normal user set severity to normal
# 8) Normal user adds a comment #3
# 9) Normal user assigns the bug to itself
# Normal User should get following bugmail (in order):
# 1) A bug is created
# 2) Normal user sets severity to blocker
# 3) Admin sets severity to trivial
# 4) Admin adds a comment #2
# 5) Admin removes CC of normal user
# 6) Admin assigns the bug to itself
# 7) Normal user sets severity to normal
#
# Admin should get following request mails (in order):
# 1) Normal user denies a flag requested by the admin
# Normal user should get following request mails (in order):
# 1) Admin requests a flag from normal user
#
# NOTE that only correct bugmail is verified by the test script because
# sending request mail is not indicated on the UI.
# Set admin Email Prefs (via link in footer)
log_in($sel, $config, 'admin');
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Email Notifications");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok("Email Notifications");
$sel->click_ok("//input[\@value='Disable All Mail']");
$sel->click_ok("email-0-1", undef, 'Set "I\'m added to or removed from this capacity" for Assignee role');
$sel->click_ok("email-0-5", undef, 'Set "The priority, status, severity, or milestone changes" for Assignee role');
$sel->click_ok("email-0-2", undef, 'Set "New comments are added" for Assignee role');
$sel->click_ok("email-0-0", undef, 'Set "Any field not mentioned above changes" for Assignee role');
$sel->click_ok("email-3-8", undef, 'Set "The CC field changes" for CCed role');
$sel->click_ok("email-1-10", undef, 'Set "A new bug is created" for QA Contact role');
$sel->click_ok("email-100-101", undef, 'Set "Email me when someone sets a flag I asked for" global option');
# Restore the old 4.2 behavior for 'Disable All Mail'.
foreach my $col (0..3) {
foreach my $row (50..51) {
$sel->click_ok("neg-email-$col-$row");
}
}
$sel->value_is("email-0-1", "on");
$sel->value_is("email-0-10", "off");
$sel->value_is("email-0-6", "off");
$sel->value_is("email-0-5", "on");
$sel->value_is("email-0-2", "on");
$sel->value_is("email-0-3", "off");
$sel->value_is("email-0-4", "off");
$sel->value_is("email-0-7", "off");
$sel->value_is("email-0-8", "off");
$sel->value_is("email-0-9", "off");
$sel->value_is("email-0-0", "on");
$sel->value_is("neg-email-0-50", "off");
$sel->value_is("neg-email-0-51", "off");
$sel->value_is("email-1-1", "off");
$sel->value_is("email-1-10", "on");
$sel->value_is("email-1-6", "off");
$sel->value_is("email-1-5", "off");
$sel->value_is("email-1-2", "off");
$sel->value_is("email-1-3", "off");
$sel->value_is("email-1-4", "off");
$sel->value_is("email-1-7", "off");
$sel->value_is("email-1-8", "off");
$sel->value_is("email-1-9", "off");
$sel->value_is("email-1-0", "off");
$sel->value_is("neg-email-1-50", "off");
$sel->value_is("neg-email-1-51", "off");
ok(!$sel->is_editable("email-2-1"), 'The "I\'m added to or removed from this capacity" for Reporter role is disabled');
$sel->value_is("email-2-10", "off");
$sel->value_is("email-2-6", "off");
$sel->value_is("email-2-5", "off");
$sel->value_is("email-2-2", "off");
$sel->value_is("email-2-3", "off");
$sel->value_is("email-2-4", "off");
$sel->value_is("email-2-7", "off");
$sel->value_is("email-2-8", "off");
$sel->value_is("email-2-9", "off");
$sel->value_is("email-2-0", "off");
$sel->value_is("neg-email-2-50", "off");
$sel->value_is("neg-email-2-51", "off");
$sel->value_is("email-3-1", "off");
$sel->value_is("email-3-10", "off");
$sel->value_is("email-3-6", "off");
$sel->value_is("email-3-5", "off");
$sel->value_is("email-3-2", "off");
$sel->value_is("email-3-3", "off");
$sel->value_is("email-3-4", "off");
$sel->value_is("email-3-7", "off");
$sel->value_is("email-3-8", "on");
$sel->value_is("email-3-9", "off");
$sel->value_is("email-3-0", "off");
$sel->value_is("neg-email-3-50", "off");
$sel->value_is("neg-email-3-51", "off");
$sel->value_is("email-100-100", "off");
$sel->value_is("email-100-101", "on");
$sel->click_ok("update", undef, "Submit modified admin email preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok("The changes to your email notifications have been saved.");
# Set "After changing a bug" default preference to "Show the updated bug"
# This simplifies bug changes below
go_to_admin($sel);
$sel->click_ok("link=Default Preferences");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Default Preferences");
$sel->select_ok("post_bug_submit_action", "label=Show the updated bug");
$sel->click_ok("update");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Default Preferences");
# Set normal user Email Prefs
logout($sel);
log_in($sel, $config, 'editbugs');
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Email Notifications");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Email Notifications");
$sel->is_text_present_ok("Email Notifications");
$sel->click_ok("//input[\@value='Enable All Mail']");
$sel->click_ok("email-3-1", undef, 'Clear "I\'m added to or removed from this capacity" for CCed role');
$sel->click_ok("email-3-5", undef, 'Clear "The priority, status, severity, or milestone changes" for CCed role');
$sel->click_ok("email-2-2", undef, 'Clear "New comments are added" for Reporter role');
$sel->click_ok("email-3-2", undef, 'Clear "New comments are added" for CCed role');
$sel->click_ok("email-2-8", undef, 'Clear "The CC field changes" for Reporter role');
$sel->click_ok("email-3-8", undef, 'Clear "The CC field changes" for CCed role');
$sel->click_ok("email-2-0", undef, 'Clear "Any field not mentioned above changes" for Reporter role');
$sel->click_ok("email-3-0", undef, 'Clear "Any field not mentioned above changes" for CCed role');
$sel->click_ok("neg-email-0-51", undef, 'Set "Change was made by me" override for Assignee role');
$sel->click_ok("email-100-101", undef, 'Clear "Email me when someone sets a flag I asked for" global option');
$sel->value_is("email-0-1", "on");
$sel->value_is("email-0-10", "on");
$sel->value_is("email-0-6", "on");
$sel->value_is("email-0-5", "on");
$sel->value_is("email-0-2", "on");
$sel->value_is("email-0-3", "on");
$sel->value_is("email-0-4", "on");
$sel->value_is("email-0-7", "on");
$sel->value_is("email-0-8", "on");
$sel->value_is("email-0-9", "on");
$sel->value_is("email-0-0", "on");
$sel->value_is("neg-email-0-50", "off");
$sel->value_is("neg-email-0-51", "on");
$sel->value_is("email-1-1", "on");
$sel->value_is("email-1-10", "on");
$sel->value_is("email-1-6", "on");
$sel->value_is("email-1-5", "on");
$sel->value_is("email-1-2", "on");
$sel->value_is("email-1-3", "on");
$sel->value_is("email-1-4", "on");
$sel->value_is("email-1-7", "on");
$sel->value_is("email-1-8", "on");
$sel->value_is("email-1-9", "on");
$sel->value_is("email-1-0", "on");
$sel->value_is("neg-email-1-50", "off");
$sel->value_is("neg-email-1-51", "off");
ok(!$sel->is_editable("email-2-1"), 'The "I\'m added to or removed from this capacity" for Reporter role is disabled');
$sel->value_is("email-2-10", "on");
$sel->value_is("email-2-6", "on");
$sel->value_is("email-2-5", "on");
$sel->value_is("email-2-2", "off");
$sel->value_is("email-2-3", "on");
$sel->value_is("email-2-4", "on");
$sel->value_is("email-2-7", "on");
$sel->value_is("email-2-8", "off");
$sel->value_is("email-2-9", "on");
$sel->value_is("email-2-0", "off");
$sel->value_is("neg-email-2-50", "off");
$sel->value_is("neg-email-2-51", "off");
$sel->value_is("email-3-1", "off");
$sel->value_is("email-3-10", "on");
$sel->value_is("email-3-6", "on");
$sel->value_is("email-3-5", "off");
$sel->value_is("email-3-2", "off");
$sel->value_is("email-3-3", "on");
$sel->value_is("email-3-4", "on");
$sel->value_is("email-3-7", "on");
$sel->value_is("email-3-8", "off");
$sel->value_is("email-3-9", "on");
$sel->value_is("email-3-0", "off");
$sel->value_is("neg-email-3-50", "off");
$sel->value_is("neg-email-3-51", "off");
$sel->value_is("email-100-100", "on");
$sel->value_is("email-100-101", "off");
$sel->click_ok("update", undef, "Submit modified normal user email preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok("The changes to your email notifications have been saved.");
# Create a test bug (bugmail to both normal user and admin)
file_bug_in_product($sel, "Another Product");
$sel->select_ok("component", "label=c1");
my $bug_summary = "Selenium Email Preference test bug";
$sel->type_ok("short_desc", $bug_summary, "Enter bug summary");
$sel->type_ok("comment", "Created by Selenium to test Email Notifications", "Enter bug description");
$sel->type_ok("assigned_to", $config->{editbugs_user_login});
$sel->type_ok("qa_contact", $config->{admin_user_login});
$sel->type_ok("cc", $config->{admin_user_login});
my $bug1_id = create_bug($sel, $bug_summary);
verify_bugmail_recipients($sel, RCPT_BOTH);
# Make normal user changes (first pass)
#
go_to_bug($sel, $bug1_id);
# Severity change (bugmail to normal user but not admin)
$sel->select_ok("bug_severity", "label=blocker");
$sel->selected_label_is("bug_severity", "blocker");
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_NORMAL);
# Add a comment (bugmail to no one)
$sel->type_ok("comment", "This is a Selenium generated normal user test comment 1 of 2. (No bugmail should be generated for this.)");
$sel->value_is("comment", "This is a Selenium generated normal user test comment 1 of 2. (No bugmail should be generated for this.)");
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_NONE);
# Add normal user to CC list (bugmail to admin but not normal user)
$sel->type_ok("newcc", $config->{editbugs_user_login});
$sel->value_is("newcc", $config->{editbugs_user_login});
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_ADMIN);
# Request a flag from admin (bugmail to no one, request mail to no one)
$sel->select_ok("flag_type-1", "label=?");
$sel->type_ok("requestee_type-1", $config->{admin_user_login});
$sel->value_is("requestee_type-1", $config->{admin_user_login});
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_NONE);
# Make admin changes
#
logout($sel);
log_in($sel, $config, 'admin');
go_to_bug($sel, $bug1_id);
# Severity change (bugmail to normal user but not admin)
$sel->select_ok("bug_severity", "label=trivial");
$sel->selected_label_is("bug_severity", "trivial");
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_NORMAL);
# Add a comment (bugmail to normal user but not admin)
$sel->type_ok("comment", "This is a Selenium generated admin user test comment. (Only normal user should get bugmail for this.)");
$sel->value_is("comment", "This is a Selenium generated admin user test comment. (Only normal user should get bugmail for this.)");
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_NORMAL);
# Remove normal user from CC list (bugmail to both normal user and admin)
$sel->click_ok("removecc");
$sel->add_selection_ok("cc", "label=$config->{editbugs_user_login}");
$sel->value_is("removecc", "on");
$sel->selected_label_is("cc", $config->{editbugs_user_login});
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_BOTH);
# Reassign bug to admin user (bugmail to both normal user and admin)
$sel->type_ok("assigned_to", $config->{admin_user_login});
$sel->value_is("assigned_to", $config->{admin_user_login});
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_BOTH);
# Request a flag from normal user (bugmail to admin but not normal user and request mail to admin)
$sel->select_ok("flag_type-1", "label=?");
$sel->type_ok("requestee_type-1", $config->{editbugs_user_login});
$sel->value_is("requestee_type-1", $config->{editbugs_user_login});
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_ADMIN);
# Grant a normal user flag request (bugmail to admin but not normal user and request mail to no one)
my $flag1_id = set_flag($sel, $config->{admin_user_login}, "?", "+");
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_ADMIN);
# Make normal user changes (second pass)
#
logout($sel);
log_in($sel, $config, 'editbugs');
go_to_bug($sel, $bug1_id);
# Severity change (bugmail to both admin and normal user)
$sel->select_ok("bug_severity", "label=normal");
$sel->selected_label_is("bug_severity", "normal");
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_BOTH);
# Add a comment (bugmail to admin but not normal user)
$sel->type_ok("comment", "This is a Selenium generated normal user test comment 2 of 2. (Only admin should get bugmail for this.)");
$sel->value_is("comment", "This is a Selenium generated normal user test comment 2 of 2. (Only admin should get bugmail for this.)");
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_ADMIN);
# Reassign to normal user (bugmail to admin but not normal user)
$sel->type_ok("assigned_to", $config->{editbugs_user_login});
$sel->value_is("assigned_to", $config->{editbugs_user_login});
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_ADMIN);
# Deny a flag requested by admin (bugmail to no one and request mail to admin)
my $flag2_id = set_flag($sel, $config->{editbugs_user_login}, "?", "-");
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_NONE);
# Cancel both flags (bugmail and request mail to no one)
set_flag($sel, undef, "+", "X", $flag1_id);
set_flag($sel, undef, "-", "X", $flag2_id);
edit_bug($sel, $bug1_id, $bug_summary);
verify_bugmail_recipients($sel, RCPT_NONE);
logout($sel);
# Set "After changing a bug" default preference back to "Do Nothing".
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Default Preferences");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Default Preferences");
$sel->select_ok("post_bug_submit_action", "label=Do Nothing");
$sel->click_ok("update");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Default Preferences");
logout($sel);
# Help functions
sub verify_bugmail_recipients {
my ($sel, $rcpt_sentto) = @_;
my $wanted_sentto;
my $err = 0;
# Verify sentto field
my @email_sentto
= sort split(/, /, $sel->get_text("//dt[text()='Email sent to:']/following-sibling::dd"));
if ($rcpt_sentto == RCPT_BOTH) {
$wanted_sentto = \@email_both;
is_deeply(\@email_sentto, $wanted_sentto, "Bugmail sent to both")
or $err = 1;
}
elsif ($rcpt_sentto == RCPT_ADMIN) {
$wanted_sentto = \@email_admin;
is_deeply(\@email_sentto, $wanted_sentto, "Bugmail sent to admin")
or $err = 1;
}
elsif ($rcpt_sentto == RCPT_NORMAL) {
$wanted_sentto = \@email_normal;
is_deeply(\@email_sentto, $wanted_sentto, "Bugmail sent to normal user")
or $err = 1;
} else {
$wanted_sentto = \@email_none;
is_deeply(\@email_sentto, $wanted_sentto, "Bugmail sent to no one")
or $err = 1;
}
# In case of an error, retrieve and show diagnostics info
if ($err) {
diag("Sent, actual : " . join(', ', @email_sentto));
diag("Sent, wanted : " . join(', ', @$wanted_sentto));
diag("Changer : " . trim($sel->get_text('//a[contains(@href, "logout")]/../text()[3]')));
diag("Reporter : " . $sel->get_attribute('//th[contains(text(), "Reported:")]/following-sibling::td//a@title'));
diag("Assignee : " . $sel->get_value('assigned_to'));
diag("QA contact : " . $sel->get_value('qa_contact'));
diag("CC List : " . join(', ', $sel->get_select_options('cc')));
}
}
sub set_flag {
my ($sel, $login, $curval, $newval, $prev_id) = @_;
# Retrieve flag id for the flag to be set
my $flag_id = $prev_id;
if (defined $login) {
my $flag_name = $sel->get_attribute("//table[\@id='flags']//input[\@value='$login']\@name");
$flag_name =~ /^requestee-(\d+)$/;
$flag_id = $1;
}
# Set new value for the flag (verifies current value)
$sel->select_ok("//select[\@id=\"flag-$flag_id\"]/option[\@value=\"$curval\" and \@selected]/..", "value=$newval", "Set flag ID $flag_id to $newval from $curval");
return $flag_id;
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Very simple test script to test if bug creation with minimal data
# passes successfully for different user privileges.
#
# More elaborate tests exist in other scripts. This doesn't mean this
# one could not be improved a bit.
my $bug_summary = "Bug created by Selenium";
foreach my $user (qw(admin unprivileged canconfirm)) {
log_in($sel, $config, $user);
file_bug_in_product($sel, "TestProduct");
$sel->type_ok("short_desc", $bug_summary, "Enter bug summary");
$sel->type_ok("comment", "--- Bug created by Selenium ---", "Enter bug description");
create_bug($sel, $bug_summary);
logout($sel);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
# We have to upload files from the local computer. This requires
# chrome privileges.
my ($sel, $config) = get_selenium(CHROME_MODE);
# First create a flag type for bugs.
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Flags");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Administer Flag Types");
$sel->click_ok("link=Create Flag Type for Bugs");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
$sel->type_ok("name", "SeleniumBugFlag1Test");
$sel->type_ok("description", "bugflag1");
$sel->select_ok("product", "label=TestProduct");
$sel->click_ok("categoryAction-include");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
$sel->remove_all_selections_ok("inclusion_to_remove");
$sel->add_selection_ok("inclusion_to_remove", "label=__Any__:__Any__");
$sel->click_ok("categoryAction-removeInclusion");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
$sel->select_ok("product", "label=QA-Selenium-TEST");
$sel->click_ok("categoryAction-exclude");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
$sel->select_ok("product", "label=QA-Selenium-TEST");
$sel->click_ok("categoryAction-include");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
my @inclusion = $sel->get_select_options("inclusion_to_remove");
ok(scalar @inclusion == 2, "The inclusion list contains 2 elements");
ok(grep($_ eq "QA-Selenium-TEST:__Any__", @inclusion), "QA-Selenium-TEST:__Any__ is in the inclusion list");
ok(grep($_ eq "TestProduct:__Any__", @inclusion), "TestProduct:__Any__ is in the inclusion list");
my @exclusion = $sel->get_select_options("exclusion_to_remove");
ok(scalar @exclusion == 1, "The exclusion list contains 1 element");
ok($exclusion[0] eq "QA-Selenium-TEST:__Any__", "QA-Selenium-TEST:__Any__ is in the exclusion list");
$sel->type_ok("sortkey", "900");
$sel->value_is("cc_list", "");
$sel->value_is("is_active", "on");
$sel->value_is("is_requestable", "on");
$sel->value_is("is_requesteeble", "on");
$sel->value_is("is_multiplicable", "on");
$sel->select_ok("grant_group", "label=admin");
$sel->select_ok("request_group", "label=(no group)");
$sel->click_ok("save");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type 'SeleniumBugFlag1Test' Created");
$sel->is_text_present_ok("The flag type SeleniumBugFlag1Test has been created.");
my $flagtype_url = $sel->get_attribute('link=SeleniumBugFlag1Test@href');
$flagtype_url =~ /id=(\d+)$/;
my $flagtype1_id = $1;
# Clone the flag type, but set the request group to 'editbugs' and the sortkey to 950.
$sel->click_ok("//a[\@href='editflagtypes.cgi?action=copy&id=$flagtype1_id']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs Based on SeleniumBugFlag1Test");
$sel->type_ok("name", "SeleniumBugFlag2Test");
$sel->type_ok("description", "bugflag2");
@inclusion = $sel->get_select_options("inclusion_to_remove");
ok(scalar @inclusion == 2, "The inclusion list contains 2 elements");
ok(grep($_ eq "QA-Selenium-TEST:__Any__", @inclusion), "QA-Selenium-TEST:__Any__ is in the inclusion list");
ok(grep($_ eq "TestProduct:__Any__", @inclusion), "TestProduct:__Any__ is in the inclusion list");
@exclusion = $sel->get_select_options("exclusion_to_remove");
ok(scalar @exclusion == 1, "The exclusion list contains 1 element");
ok($exclusion[0] eq "QA-Selenium-TEST:__Any__", "QA-Selenium-TEST:__Any__ is in the exclusion list");
$sel->type_ok("sortkey", "950");
$sel->value_is("is_active", "on");
$sel->value_is("is_requestable", "on");
$sel->value_is("is_requesteeble", "on");
$sel->value_is("is_multiplicable", "on");
$sel->type_ok("cc_list", $config->{canconfirm_user_login});
$sel->selected_label_is("grant_group", "admin");
$sel->select_ok("request_group", "label=editbugs");
$sel->click_ok("save");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type 'SeleniumBugFlag2Test' Created");
$sel->is_text_present_ok("The flag type SeleniumBugFlag2Test has been created.");
$flagtype_url = $sel->get_attribute('link=SeleniumBugFlag2Test@href');
$flagtype_url =~ /id=(\d+)$/;
my $flagtype2_id = $1;
# Clone the first flag type again, but with different attributes.
$sel->click_ok("//a[\@href='editflagtypes.cgi?action=copy&id=$flagtype1_id']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs Based on SeleniumBugFlag1Test");
$sel->type_ok("name", "SeleniumBugFlag3Test");
$sel->type_ok("description", "bugflag3");
$sel->type_ok("sortkey", "980");
$sel->value_is("is_active", "on");
$sel->value_is("is_requestable", "on");
$sel->uncheck_ok("is_requesteeble");
$sel->uncheck_ok("is_multiplicable");
$sel->value_is("cc_list", "");
$sel->select_ok("grant_group", "label=(no group)");
$sel->selected_label_is("request_group", "(no group)");
$sel->click_ok("save");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type 'SeleniumBugFlag3Test' Created");
$sel->is_text_present_ok("The flag type SeleniumBugFlag3Test has been created.");
$flagtype_url = $sel->get_attribute('link=SeleniumBugFlag3Test@href');
$flagtype_url =~ /id=(\d+)$/;
my $flagtype3_id = $1;
# We now create a flag type for attachments.
$sel->click_ok("link=Create Flag Type For Attachments");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Attachments");
$sel->type_ok("name", "SeleniumAttachmentFlag1Test");
$sel->type_ok("description", "attachmentflag1");
$sel->select_ok("product", "label=TestProduct");
$sel->click_ok("categoryAction-include");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Attachments");
$sel->remove_all_selections_ok("inclusion_to_remove");
$sel->add_selection_ok("inclusion_to_remove", "label=__Any__:__Any__");
$sel->click_ok("categoryAction-removeInclusion");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Attachments");
@inclusion = $sel->get_select_options("inclusion_to_remove");
ok(scalar @inclusion == 1, "The inclusion list contains 1 element");
ok($inclusion[0] eq "TestProduct:__Any__", "TestProduct:__Any__ is in the exclusion list");
$sel->type_ok("sortkey", "700");
$sel->value_is("cc_list", "");
$sel->select_ok("grant_group", "label=editbugs");
$sel->select_ok("request_group", "label=canconfirm");
$sel->click_ok("save");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type 'SeleniumAttachmentFlag1Test' Created");
$sel->is_text_present_ok("The flag type SeleniumAttachmentFlag1Test has been created.");
$flagtype_url = $sel->get_attribute('link=SeleniumAttachmentFlag1Test@href');
$flagtype_url =~ /id=(\d+)$/;
my $aflagtype1_id = $1;
# Clone the flag type.
$sel->click_ok("//a[\@href='editflagtypes.cgi?action=copy&id=$aflagtype1_id']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Attachments Based on SeleniumAttachmentFlag1Test");
$sel->type_ok("name", "SeleniumAttachmentFlag2Test");
$sel->type_ok("description", "attachmentflag2");
@inclusion = $sel->get_select_options("inclusion_to_remove");
ok(scalar @inclusion == 1, "The inclusion list contains 1 element");
ok($inclusion[0] eq "TestProduct:__Any__", "TestProduct:__Any__ is in the exclusion list");
$sel->type_ok("sortkey", "750");
$sel->type_ok("cc_list", $config->{admin_user_login});
$sel->uncheck_ok("is_multiplicable");
$sel->select_ok("grant_group", "label=(no group)");
$sel->select_ok("request_group", "label=(no group)");
$sel->click_ok("save");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type 'SeleniumAttachmentFlag2Test' Created");
$sel->is_text_present_ok("The flag type SeleniumAttachmentFlag2Test has been created.");
$flagtype_url = $sel->get_attribute('link=SeleniumAttachmentFlag2Test@href');
$flagtype_url =~ /id=(\d+)$/;
my $aflagtype2_id = $1;
# Clone the flag type again, and set it as inactive.
$sel->click_ok("//a[\@href='editflagtypes.cgi?action=copy&id=$aflagtype1_id']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Attachments Based on SeleniumAttachmentFlag1Test");
$sel->type_ok("name", "SeleniumAttachmentFlag3Test");
$sel->type_ok("description", "attachmentflag3");
$sel->type_ok("sortkey", "800");
$sel->uncheck_ok("is_active");
$sel->click_ok("save");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type 'SeleniumAttachmentFlag3Test' Created");
$sel->is_text_present_ok("The flag type SeleniumAttachmentFlag3Test has been created.");
$flagtype_url = $sel->get_attribute('link=SeleniumAttachmentFlag3Test@href');
$flagtype_url =~ /id=(\d+)$/;
my $aflagtype3_id = $1;
# All flag types have been created. Now "real" tests can start.
file_bug_in_product($sel, 'TestProduct');
my $bug_summary = "test flags";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "this bug is used by Selenium to test flags");
# Restrict the bug to the Master group. That's important for subsequent tests!
$sel->check_ok('//input[@name="groups" and @value="Master"]');
my $bug1_id = create_bug($sel, $bug_summary);
# All 3 bug flag types must be available; we are in the TestProduct product.
$sel->is_text_present_ok("SeleniumBugFlag1Test");
# We specify //select or //input, just to be sure. This is not required, though.
$sel->is_element_present_ok("//select[\@id='flag_type-$flagtype1_id']");
$sel->is_element_present_ok("//input[\@id='requestee_type-$flagtype1_id']");
# If fields are of the correct type above, we assume this is still true below.
$sel->is_text_present_ok("SeleniumBugFlag2Test");
$sel->is_element_present_ok("flag_type-$flagtype2_id");
$sel->is_element_present_ok("requestee_type-$flagtype2_id");
$sel->is_text_present_ok("SeleniumBugFlag3Test");
$sel->is_element_present_ok("flag_type-$flagtype3_id");
ok(!$sel->is_element_present("requestee_type-$flagtype3_id"), "SeleniumBugFlag3Test is not specifically requestable");
# This is intentional to generate "flagmail". Some flags have a CC list
# associated with them, some others don't. This is to catch crashes due to
# the MTA.
$sel->select_ok("flag_type-$flagtype1_id", "label=?");
$sel->select_ok("flag_type-$flagtype2_id", "label=?");
$sel->select_ok("flag_type-$flagtype3_id", "label=?");
$sel->type_ok("comment", "Setting all 3 flags to ?");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
# We need to store the new flag IDs.
$sel->is_text_present_ok("$config->{admin_user_username}: SeleniumBugFlag1Test");
my $flag1_1_id = $sel->get_attribute('//select[@title="bugflag1"]@id');
$flag1_1_id =~ s/flag-//;
$sel->is_text_present_ok("$config->{admin_user_username}: SeleniumBugFlag2Test");
my $flag2_1_id = $sel->get_attribute('//select[@title="bugflag2"]@id');
$flag2_1_id =~ s/flag-//;
$sel->is_text_present_ok("$config->{admin_user_username}: SeleniumBugFlag3Test");
my $flag3_1_id = $sel->get_attribute('//select[@title="bugflag3"]@id');
$flag3_1_id =~ s/flag-//;
$sel->is_text_present_ok("addl. SeleniumBugFlag1Test");
$sel->is_text_present_ok("addl. SeleniumBugFlag2Test");
ok(!$sel->is_text_present("addl. SeleniumBugFlag3Test"), "SeleniumBugFlag3Test is not multiplicable");
$sel->select_ok("flag_type-$flagtype1_id", "label=+");
$sel->select_ok("flag_type-$flagtype2_id", "label=-");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
# Now let's test requestees. SeleniumBugFlag2Test requires the requestee
# to be in the editbugs group.
$sel->select_ok("flag_type-$flagtype1_id", "label=?");
$sel->type_ok("requestee_type-$flagtype1_id", $config->{admin_user_login});
$sel->select_ok("flag_type-$flagtype2_id", "label=?");
$sel->type_ok("requestee_type-$flagtype2_id", $config->{unprivileged_user_login});
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Requestee Not Authorized");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
$sel->type_ok("requestee_type-$flagtype2_id", $config->{admin_user_login});
edit_bug_and_return($sel, $bug1_id, $bug_summary);
# Final tests for bug flags.
$sel->select_ok("flag-$flag1_1_id", "value=X");
$sel->select_ok("flag-$flag2_1_id", "label=+");
$sel->select_ok("flag-$flag3_1_id", "label=-");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
# Now we test attachment flags.
$sel->click_ok("link=Add an attachment");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create New Attachment for Bug #$bug1_id");
$sel->type_ok("data", $config->{attachment_file});
$sel->type_ok("description", "patch, v1");
$sel->check_ok("ispatch");
$sel->is_text_present_ok("SeleniumAttachmentFlag1Test");
$sel->is_text_present_ok("SeleniumAttachmentFlag2Test");
ok(!$sel->is_text_present("SeleniumAttachmentFlag3Test"), "Inactive SeleniumAttachmentFlag3Test flag type not displayed");
# Let's generate some "flagmail", first with no requestee.
$sel->select_ok("flag_type-$aflagtype1_id", "label=?");
$sel->select_ok("flag_type-$aflagtype2_id", "label=?");
$sel->type_ok("comment", "patch for testing purposes only");
edit_bug($sel, $bug1_id, $bug_summary, {id => "create"});
# Store the flag ID.
my $alink = $sel->get_attribute('//a[@title="patch, v1"]@href');
$alink =~ /id=(\d+)/;
my $attachment1_id = $1;
# Now create another attachment, and set requestees.
$sel->click_ok("link=Create Another Attachment to Bug $bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create New Attachment for Bug #$bug1_id");
$sel->type_ok("data", $config->{attachment_file});
$sel->type_ok("description", "patch, v2");
$sel->check_ok("ispatch");
# Mark the previous attachment as obsolete.
$sel->check_ok($attachment1_id);
$sel->select_ok("flag_type-$aflagtype1_id", "label=?");
$sel->type_ok("requestee_type-$aflagtype1_id", $config->{admin_user_login});
$sel->select_ok("flag_type-$aflagtype2_id", "label=?");
# The requestee is not in the Master group, and so he cannot view the bug.
# He must be silently skipped from the requestee field.
$sel->type_ok("requestee_type-$aflagtype2_id", $config->{unprivileged_user_login});
$sel->type_ok("comment", "second patch, with requestee");
edit_bug($sel, $bug1_id, $bug_summary, {id => "create"});
$alink = $sel->get_attribute('//a[@title="patch, v2"]@href');
$alink =~ /id=(\d+)/;
my $attachment2_id = $1;
# Create a third attachment, but we now set the MIME type manually.
$sel->click_ok("link=Create Another Attachment to Bug $bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create New Attachment for Bug #$bug1_id");
$sel->type_ok("data", $config->{attachment_file});
$sel->type_ok("description", "patch, v3");
$sel->click_ok("list");
$sel->select_ok("contenttypeselection", "label=plain text (text/plain)");
$sel->select_ok("flag_type-$aflagtype1_id", "label=+");
$sel->type_ok("comment", "one +, the other one blank");
edit_bug($sel, $bug1_id, $bug_summary, {id => "create"});
$alink = $sel->get_attribute('//a[@title="patch, v3"]@href');
$alink =~ /id=(\d+)/;
my $attachment3_id = $1;
# Display the bug and check flags are correctly set.
$sel->click_ok("link=bug $bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
$sel->is_text_present_ok("$config->{admin_user_username}: SeleniumAttachmentFlag1Test? ($config->{admin_user_username})");
$sel->is_text_present_ok("$config->{admin_user_username}: SeleniumAttachmentFlag2Test?");
$sel->is_text_present_ok("$config->{admin_user_username}: SeleniumAttachmentFlag1Test+");
# We marked the first attachment as obsolete, so it should have no flag on it.
$sel->is_text_present_ok("no flags");
# Make the bug public and log out.
$sel->uncheck_ok('//input[@name="groups" and @value="Master"]');
edit_bug($sel, $bug1_id, $bug_summary);
logout($sel);
# As an unprivileged user, try to edit flags.
log_in($sel, $config, 'unprivileged');
go_to_bug($sel, $bug1_id);
# No privs are required to clear this flag.
$sel->select_ok("flag-$flag3_1_id", "value=X");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
# editbugs privs are required to clear this flag, so no other option
# should be displayed besides the currently set "+".
my @flag_states = $sel->get_select_options("flag-$flag2_1_id");
ok(scalar(@flag_states) == 1 && $flag_states[0] eq '+', "Single flag state '+' available");
# Powerless users cannot set the flag to +, but setting it to ? is allowed.
@flag_states = $sel->get_select_options("flag_type-$flagtype1_id");
ok(scalar @flag_states == 2, "Two flag states available");
ok(grep($_ eq '?', @flag_states), "Flag state '?' available");
# A powerless user cannot edit someone else's attachment flags.
$sel->click_ok("//a[\@href='attachment.cgi?id=$attachment2_id&action=edit']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/Attachment $attachment2_id Details for Bug $bug1_id/);
ok($sel->is_element_present('//select[@title="attachmentflag2" and @disabled]'),
"Attachment flags are not editable by a powerless user");
# Add an attachment and set flags on it.
$sel->click_ok("//a[contains(\@href, 'show_bug.cgi?id=$bug1_id')]");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id/);
$sel->click_ok("link=Add an attachment");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create New Attachment for Bug #$bug1_id");
$sel->type_ok("data", $config->{attachment_file});
$sel->type_ok("description", "patch, v4");
$sel->value_is("ispatch", "off");
$sel->value_is("autodetect", "on");
# canconfirm/editbugs privs are required to edit this flag.
ok(!$sel->is_element_present("flag_type-$aflagtype1_id"), "Flag type 'SeleniumAttachmentFlag1Test' not displayed to powerless users");
# No privs are required to edit this flag.
$sel->select_ok("flag_type-$aflagtype2_id", "label=+");
$sel->type_ok("comment", "granting again");
edit_bug_and_return($sel, $bug1_id, $bug_summary, {id => "create"});
$sel->is_text_present_ok("$config->{unprivileged_user_username}: SeleniumAttachmentFlag2Test+");
logout($sel);
# Final tests as an admin. He has editbugs privs, so he can edit
# someone else's patch.
log_in($sel, $config, 'admin');
go_to_bug($sel, $bug1_id);
$sel->click_ok("//a[\@href='attachment.cgi?id=${attachment3_id}&action=edit']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/Attachment $attachment3_id Details for Bug $bug1_id/);
$sel->select_ok('//select[@title="attachmentflag1"]', "label=+");
edit_bug($sel, $bug1_id, $bug_summary, {id => "update"});
# It's time to delete all created flag types.
go_to_admin($sel);
$sel->click_ok("link=Flags");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Administer Flag Types");
foreach my $flagtype ([$flagtype1_id, "SeleniumBugFlag1Test"], [$flagtype2_id, "SeleniumBugFlag2Test"],
[$flagtype3_id, "SeleniumBugFlag3Test"], [$aflagtype1_id, "SeleniumAttachmentFlag1Test"],
[$aflagtype2_id, "SeleniumAttachmentFlag2Test"], [$aflagtype3_id, "SeleniumAttachmentFlag3Test"])
{
my $flag_id = $flagtype->[0];
my $flag_name = $flagtype->[1];
$sel->click_ok("//a[\@href='editflagtypes.cgi?action=confirmdelete&id=$flag_id']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Confirm Deletion of Flag Type '$flag_name'");
$sel->click_ok("link=Yes, delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type '$flag_name' Deleted");
my $msg = trim($sel->get_text("message"));
ok($msg eq "The flag type $flag_name has been deleted.", "Flag type $flag_name deleted");
}
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
################################################################
# 2nd script about flags. This one is focused on flag behavior #
# when moving a bug from one product/component to another one. #
################################################################
# We have to upload files from the local computer. This requires
# chrome privileges.
my ($sel, $config) = get_selenium(CHROME_MODE);
# Start by creating a flag type for bugs.
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Flags");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Administer Flag Types");
$sel->click_ok("link=Create Flag Type for Bugs");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
$sel->type_ok("name", "selenium");
$sel->type_ok("description", "Available in TestProduct and Another Product/c1");
$sel->add_selection_ok("inclusion_to_remove", "label=__Any__:__Any__");
$sel->click_ok("categoryAction-removeInclusion");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
$sel->select_ok("product", "label=TestProduct");
$sel->selected_label_is("component", "__Any__");
$sel->click_ok("categoryAction-include");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
$sel->select_ok("product", "label=Another Product");
$sel->select_ok("component", "label=c1");
$sel->click_ok("categoryAction-include");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
# This flag type must have a higher sortkey than the one we will create later.
# The reason is that link=selenium will catch the first link with this name in
# the UI, so when the second flag type with this name is created, we have to
# catch it, not this one (which will be unique for now, so no worry to find it).
$sel->type_ok("sortkey", 100);
$sel->value_is("is_active", "on");
$sel->value_is("is_requestable", "on");
$sel->click_ok("is_multiplicable");
$sel->value_is("is_multiplicable", "off");
$sel->select_ok("grant_group", "label=editbugs");
$sel->select_ok("request_group", "label=canconfirm");
$sel->click_ok("save");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type 'selenium' Created");
$sel->is_text_present_ok("The flag type selenium has been created.");
# Store the flag type ID.
$sel->click_ok("link=selenium");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
my $flag_url = $sel->get_location();
$flag_url =~ /id=(\d+)/;
my $flagtype1_id = $1;
# Now create a flag type for attachments in 'Another Product'.
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->click_ok("link=Create Flag Type For Attachments");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Attachments");
$sel->type_ok("name", "selenium_review");
$sel->type_ok("description", "Review flag used by Selenium");
$sel->add_selection_ok("inclusion_to_remove", "label=__Any__:__Any__");
$sel->click_ok("categoryAction-removeInclusion");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Attachments");
$sel->select_ok("product", "label=Another Product");
$sel->click_ok("categoryAction-include");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Attachments");
$sel->type_ok("sortkey", 100);
$sel->value_is("is_active", "on");
$sel->value_is("is_requestable", "on");
$sel->click_ok("is_multiplicable");
$sel->value_is("is_multiplicable", "off");
$sel->selected_label_is("grant_group", "(no group)");
$sel->selected_label_is("request_group", "(no group)");
$sel->click_ok("save");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type 'selenium_review' Created");
$sel->is_text_present_ok("The flag type selenium_review has been created.");
# Store the flag type ID.
$sel->click_ok("link=selenium_review");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$flag_url = $sel->get_location();
$flag_url =~ /id=(\d+)/;
my $aflagtype1_id = $1;
# Create a 2nd flag type for attachments, with the same name
# as the 1st one, but now *excluded* from 'Another Product'.
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->click_ok("link=Create Flag Type For Attachments");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->type_ok("name", "selenium_review");
$sel->type_ok("description", "Another review flag used by Selenium");
$sel->select_ok("product", "label=Another Product");
$sel->click_ok("categoryAction-include");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Attachments");
$sel->type_ok("sortkey", 50);
$sel->value_is("is_active", "on");
$sel->value_is("is_requestable", "on");
$sel->value_is("is_multiplicable", "on");
$sel->select_ok("grant_group", "label=editbugs");
$sel->select_ok("request_group", "label=canconfirm");
$sel->click_ok("save");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type 'selenium_review' Created");
# Store the flag type ID.
$sel->click_ok("link=selenium_review");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$flag_url = $sel->get_location();
$flag_url =~ /id=(\d+)/;
my $aflagtype2_id = $1;
# We are done with the admin tasks. Now play with flags in bugs.
file_bug_in_product($sel, 'TestProduct');
$sel->select_ok("flag_type-$flagtype1_id", "label=+");
my $bug_summary = "The selenium flag should be kept on product change";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "pom");
$sel->click_ok('//input[@value="Add an attachment"]');
$sel->type_ok("data", $config->{attachment_file});
$sel->type_ok("description", "small patch");
$sel->click_ok("ispatch");
$sel->value_is("ispatch", "on");
ok(!$sel->is_element_present("flag_type-$aflagtype1_id"), "Flag type $aflagtype1_id not available in TestProduct");
$sel->select_ok("flag_type-$aflagtype2_id", "label=-");
my $bug1_id = create_bug($sel, $bug_summary);
$sel->is_text_present_ok("$config->{admin_user_username}: selenium");
my $flag1_id = $sel->get_attribute('//select[@title="Available in TestProduct and Another Product/c1"]@id');
$flag1_id =~ s/flag-//;
$sel->selected_label_is("flag-$flag1_id", "+");
$sel->is_text_present_ok("$config->{admin_user_username}: selenium_review-");
# Now move the bug into the 'Another Product' product.
# Both the bug and attachment flags should survive.
$sel->select_ok("product", "label=Another Product");
$sel->type_ok("comment", "Moving to Another Product / c1. The flag should be preserved.");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Verify New Product Details...");
$sel->select_ok("component", "label=c1");
edit_bug_and_return($sel, $bug1_id, $bug_summary, {id => "change_product"});
$sel->selected_label_is("flag-$flag1_id", "+");
$sel->is_text_present_ok("$config->{admin_user_username}: selenium_review-");
# Now moving the bug into the c2 component. The bug flag
# won't survive, but the attachment flag should.
$sel->type_ok("comment", "Moving to c2. The selenium flag will be deleted.");
$sel->select_ok("component", "label=c2");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
ok(!$sel->is_element_present("flag-$flag1_id"), "The selenium bug flag didn't survive");
ok(!$sel->is_element_present("flag_type-$flagtype1_id"), "The selenium flag type doesn't exist");
$sel->is_text_present_ok("$config->{admin_user_username}: selenium_review-");
# File a bug in 'Another Product / c2' and assign it
# to a powerless user, so that he can move it later.
file_bug_in_product($sel, 'Another Product');
$sel->select_ok("component", "label=c2");
$sel->type_ok("assigned_to", $config->{unprivileged_user_login});
ok(!$sel->is_editable("flag_type-$flagtype1_id"), "The selenium bug flag type is displayed but not selectable");
$sel->select_ok("component", "label=c1");
$sel->is_editable_ok("flag_type-$flagtype1_id", "The selenium bug flag type is not selectable");
$sel->select_ok("flag_type-$flagtype1_id", "label=?");
my $bug_summary2 = "Create a new selenium flag for c2";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", ".");
my $bug2_id = create_bug($sel, $bug_summary2);
$sel->is_text_present_ok("$config->{admin_user_username}: selenium");
my $flag2_id = $sel->get_attribute('//select[@title="Available in TestProduct and Another Product/c1"]@id');
$flag2_id =~ s/flag-//;
$sel->selected_label_is("flag-$flag2_id", '?');
# Create a 2nd bug flag type, again named 'selenium', but now
# for the 'Another Product / c2' component only.
go_to_admin($sel);
$sel->click_ok("link=Flags");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Administer Flag Types");
$sel->click_ok("link=Create Flag Type for Bugs");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
$sel->type_ok("name", "selenium");
$sel->type_ok("description", "Another flag with the selenium name");
$sel->add_selection_ok("inclusion_to_remove", "label=__Any__:__Any__");
$sel->click_ok("categoryAction-removeInclusion");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
$sel->select_ok("product", "label=Another Product");
$sel->select_ok("component", "label=c2");
$sel->click_ok("categoryAction-include");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create Flag Type for Bugs");
$sel->type_ok("sortkey", 50);
$sel->value_is("is_active", "on");
$sel->value_is("is_requestable", "on");
$sel->value_is("is_multiplicable", "on");
$sel->selected_label_is("grant_group", "(no group)");
$sel->selected_label_is("request_group", "(no group)");
$sel->click_ok("save");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type 'selenium' Created");
# Store the flag type ID.
$sel->click_ok("link=selenium");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$flag_url = $sel->get_location();
$flag_url =~ /id=(\d+)/;
my $flagtype2_id = $1;
# Now move the bug from c1 into c2. The bug flag should survive.
go_to_bug($sel, $bug2_id);
$sel->select_ok("component", "label=c2");
ok(!$sel->is_checked("set_default_assignee"), "Moving the bug into another component must not change the assignee");
$sel->type_ok("comment", "The selenium flag should be preserved.");
edit_bug_and_return($sel, $bug2_id, $bug_summary2);
$sel->selected_label_is("flag-$flag2_id", '?');
ok(!$sel->is_element_present("flag_type-$flagtype1_id"), "Flag type not available in component c2");
$sel->is_element_present_ok("flag_type-$flagtype2_id");
logout($sel);
# Powerless users can edit the 'selenium' flag being in c2.
log_in($sel, $config, 'unprivileged');
go_to_bug($sel, $bug2_id);
$sel->select_ok("flag-$flag2_id", "label=+");
edit_bug_and_return($sel, $bug2_id, $bug_summary2);
$sel->selected_label_is("flag-$flag2_id", "+");
# But moving the bug into TestProduct will delete the flag
# as the flag setter is not in the editbugs group.
$sel->select_ok("product", "label=TestProduct");
$sel->type_ok("comment", "selenium flag will be lost. I don't have editbugs privs.");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Verify New Product Details...");
edit_bug_and_return($sel, $bug2_id, $bug_summary2, {id => "change_product"});
ok(!$sel->is_element_present("flag-$flag2_id"), "Flag $flag2_id deleted");
ok(!$sel->is_element_present("flag_type-$flagtype1_id"), "Flag type 'selenium' not displayed to powerless users");
ok(!$sel->is_element_present("flag_type-$flagtype2_id"), "Flag type not available in component c1");
logout($sel);
# Time to delete created flag types.
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Flags");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Administer Flag Types");
foreach my $flagtype ([$flagtype1_id, "selenium"], [$flagtype2_id, "selenium"],
[$aflagtype1_id, "selenium_review"], [$aflagtype2_id, "selenium_review"])
{
my $flag_id = $flagtype->[0];
my $flag_name = $flagtype->[1];
$sel->click_ok("//a[\@href='editflagtypes.cgi?action=confirmdelete&id=$flag_id']");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Confirm Deletion of Flag Type '$flag_name'");
$sel->click_ok("link=Yes, delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Flag Type '$flag_name' Deleted");
my $msg = trim($sel->get_text("message"));
ok($msg eq "The flag type $flag_name has been deleted.", "Flag type $flag_name deleted");
}
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Add the new Selenium-test group.
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=Add Group");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Add group");
$sel->type_ok("name", "Selenium-test");
$sel->type_ok("desc", "Test group for Selenium");
$sel->check_ok("isactive");
$sel->uncheck_ok("insertnew");
$sel->click_ok("create");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("New Group Created");
my $group_id = $sel->get_value("group_id");
# Mark the Selenium-test group as Shown/Mandatory for TestProduct.
edit_product($sel, "TestProduct");
$sel->click_ok("link=Edit Group Access Controls:");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Group Controls for TestProduct");
$sel->is_text_present_ok("Selenium-test");
$sel->select_ok("membercontrol_${group_id}", "label=Shown");
$sel->select_ok("othercontrol_${group_id}", "label=Mandatory");
$sel->click_ok("submit");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Update group access controls for TestProduct");
# File a new bug in the TestProduct product, and restrict it to the bug group.
file_bug_in_product($sel, "TestProduct");
$sel->is_text_present_ok("Test group for Selenium");
$sel->value_is("group_${group_id}", "off"); # Must be OFF (else that's a bug)
$sel->check_ok("group_${group_id}");
my $bug_summary = "bug restricted to the Selenium group";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "should be invisible");
$sel->selected_label_is("component", "TestComponent");
my $bug1_id = create_bug($sel, $bug_summary);
$sel->is_text_present_ok("Test group for Selenium");
$sel->value_is("group_${group_id}", "on"); # Must be ON
# Look for this new bug and add it to the new "Selenium bugs" saved search.
open_advanced_search_page($sel);
$sel->remove_all_selections_ok("product");
$sel->add_selection_ok("product", "TestProduct");
$sel->remove_all_selections("bug_status");
$sel->add_selection_ok("bug_status", "UNCONFIRMED");
$sel->add_selection_ok("bug_status", "CONFIRMED");
$sel->select_ok("f1", "Group");
$sel->select_ok("o1", "is equal to");
$sel->type_ok("v1", "Selenium-test");
$sel->click_ok("Search");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("One bug found");
$sel->is_text_present_ok("bug restricted to the Selenium group");
$sel->type_ok("save_newqueryname", "Selenium bugs");
$sel->click_ok("remember");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->is_text_present_ok("OK, you have a new search named Selenium bugs");
$sel->click_ok("link=Selenium bugs");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: Selenium bugs");
$sel->is_text_present_ok("One bug found");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id restricted to the bug group");
# No longer use Selenium-test as a bug group.
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=Selenium-test");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: Selenium-test");
$sel->value_is("isactive", "on");
$sel->click_ok("isactive");
$sel->click_ok('//input[@value="Update Group"]');
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: Selenium-test");
$sel->is_text_present_ok("The group will no longer be used for bugs");
# File another new bug, now visible as the bug group is disabled.
file_bug_in_product($sel, "TestProduct");
$sel->selected_label_is("component", "TestComponent");
my $bug_summary2 = "bug restricted to the Selenium group";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", "should be *visible* when created (the group is disabled)");
ok(!$sel->is_text_present("Test group for Selenium"), "Selenium-test group unavailable");
ok(!$sel->is_element_present("group_${group_id}"), "Selenium-test checkbox not present");
my $bug2_id = create_bug($sel, $bug_summary2);
# Make sure the new bug doesn't appear in the "Selenium bugs" saved search.
$sel->click_ok("link=Selenium bugs");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: Selenium bugs");
$sel->is_text_present_ok("One bug found");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id restricted to the bug group");
ok(!$sel->is_element_present("b$bug2_id"), "Bug $bug2_id NOT restricted to the bug group");
# Re-enable the Selenium-test group as bug group. This doesn't affect
# already filed bugs as this group is not mandatory.
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=Selenium-test");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->value_is("isactive", "off");
$sel->click_ok("isactive");
$sel->title_is("Change Group: Selenium-test");
$sel->click_ok('//input[@value="Update Group"]');
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: Selenium-test");
$sel->is_text_present_ok("The group will now be used for bugs");
# Make sure the second filed bug has not been added to the bug group.
$sel->click_ok("link=Selenium bugs");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: Selenium bugs");
$sel->is_text_present_ok("One bug found");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id restricted to the bug group");
ok(!$sel->is_element_present("b$bug2_id"), "Bug $bug2_id NOT restricted to the bug group");
# Make the Selenium-test group mandatory for TestProduct.
edit_product($sel, "TestProduct");
$sel->is_text_present_ok("Selenium-test:Shown/Mandatory");
$sel->click_ok("link=Edit Group Access Controls:");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->select_ok("membercontrol_${group_id}", "Mandatory");
$sel->click_ok("submit");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Confirm Group Control Change for product 'TestProduct'");
$sel->is_text_present_ok("this group is mandatory and will be added");
$sel->click_ok("update");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Update group access controls for TestProduct");
$sel->is_text_present_ok('regexp:Adding bugs to group \'Selenium-test\' which is now mandatory for this product');
# All bugs being in TestProduct must now be restricted to the bug group.
$sel->click_ok("link=Selenium bugs");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: Selenium bugs");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id restricted to the bug group");
$sel->is_element_present_ok("b$bug2_id", undef, "Bug $bug2_id restricted to the bug group");
# File a new bug, which must automatically be restricted to the bug group.
file_bug_in_product($sel, "TestProduct");
$sel->selected_label_is("component", "TestComponent");
my $bug_summary3 = "Selenium-test group mandatory";
$sel->type_ok("short_desc", $bug_summary3);
$sel->type_ok("comment", "group enabled");
ok(!$sel->is_text_present("Test group for Selenium"), "Selenium-test group not available");
ok(!$sel->is_element_present("group_${group_id}"), "Selenium-test checkbox not present (mandatory group)");
my $bug3_id = create_bug($sel, $bug_summary3);
# Make sure all three bugs are listed as being restricted to the bug group.
$sel->click_ok("link=Selenium bugs");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: Selenium bugs");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id restricted to the bug group");
$sel->is_element_present_ok("b$bug2_id", undef, "Bug $bug2_id restricted to the bug group");
$sel->is_element_present_ok("b$bug3_id", undef, "Bug $bug3_id restricted to the bug group");
# Turn off the Selenium-test group again.
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=Selenium-test");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: Selenium-test");
$sel->value_is("isactive", "on");
$sel->click_ok("isactive");
$sel->click_ok("//input[\@value='Update Group']");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: Selenium-test");
$sel->is_text_present_ok("The group will no longer be used for bugs");
# File a bug again. It should not be added to the bug group as this one is disabled.
file_bug_in_product($sel, "TestProduct");
$sel->selected_label_is("component", "TestComponent");
my $bug_summary4 = "bug restricted to the Selenium-test group";
$sel->type_ok("short_desc", $bug_summary4);
$sel->type_ok("comment", "group disabled");
ok(!$sel->is_text_present("Test group for Selenium"), "Selenium-test group not available");
ok(!$sel->is_element_present("group_${group_id}"), "Selenium-test checkbox not present");
my $bug4_id = create_bug($sel, $bug_summary4);
# The last bug must not be in the list.
$sel->click_ok("link=Selenium bugs");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: Selenium bugs");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id restricted to the bug group");
$sel->is_element_present_ok("b$bug2_id", undef, "Bug $bug2_id restricted to the bug group");
$sel->is_element_present_ok("b$bug3_id", undef, "Bug $bug3_id restricted to the bug group");
ok(!$sel->is_element_present("b$bug4_id"), "Bug $bug4_id NOT restricted to the bug group");
# Re-enable the mandatory group. All bugs should be restricted to this bug group automatically.
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=Selenium-test");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: Selenium-test");
$sel->value_is("isactive", "off");
$sel->click_ok("isactive");
$sel->click_ok("//input[\@value='Update Group']");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: Selenium-test");
$sel->is_text_present_ok("The group will now be used for bugs");
# Make sure all bugs are restricted to the bug group.
$sel->click_ok("link=Selenium bugs");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: Selenium bugs");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id restricted to the bug group");
$sel->is_element_present_ok("b$bug2_id", undef, "Bug $bug2_id restricted to the bug group");
$sel->is_element_present_ok("b$bug3_id", undef, "Bug $bug3_id restricted to the bug group");
$sel->is_element_present_ok("b$bug4_id", undef, "Bug $bug4_id restricted to the bug group");
# Try to remove the Selenium-test group from TestProduct, but DON'T do it!
# We just want to make sure a warning is displayed about this removal.
edit_product($sel, "TestProduct");
$sel->is_text_present_ok("Selenium-test:Mandatory/Mandatory");
$sel->click_ok("link=Edit Group Access Controls:");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Group Controls for TestProduct");
$sel->is_text_present_ok("Selenium-test");
$sel->select_ok("membercontrol_${group_id}", "NA");
$sel->select_ok("othercontrol_${group_id}", "NA");
$sel->click_ok("submit");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Confirm Group Control Change for product 'TestProduct'");
$sel->is_text_present_ok("this group is no longer applicable and will be removed");
# Make sure that renaming a group which is used as a special group
# (such as insidergroup or querysharegroup) is correctly propagated
# and that you cannot delete this group.
set_parameters($sel, { "Group Security" => {"querysharegroup" => {type => "select", value => "Selenium-test"}} });
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=Selenium-test");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: Selenium-test");
$sel->type_ok("name", "X-Selenium-Y");
$sel->click_ok("update-group");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: X-Selenium-Y");
$sel->is_text_present_ok("The name was changed to 'X-Selenium-Y'");
go_to_admin($sel);
$sel->click_ok("link=Parameters");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Configuration: Required Settings");
$sel->click_ok("link=Group Security");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Configuration: Group Security");
$sel->value_is("querysharegroup", "X-Selenium-Y");
# There is no UI to delete this group, so we have to type the URL directly.
$sel->open_ok("/$config->{bugzilla_installation}/editgroups.cgi?action=del&group=$group_id");
$sel->title_is("Group not deletable");
$sel->is_text_present_ok("The group 'X-Selenium-Y' is used by the 'querysharegroup' parameter");
$sel->open_ok("/$config->{bugzilla_installation}/editgroups.cgi?action=delete&group=$group_id");
$sel->title_is("Suspicious Action");
$sel->is_text_present_ok("you have no valid token for the delete_group action while processing the 'editgroups.cgi' script");
$sel->click_ok("confirm");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Group not deletable");
$sel->is_text_present_ok("The group 'X-Selenium-Y' is used by the 'querysharegroup' parameter");
set_parameters($sel, { "Group Security" => {"querysharegroup" => {type => "select", value => ""}} });
# Revert the group name change to not mess with the subsequent tests
# which expect to see 'Selenium-test'.
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=X-Selenium-Y");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: X-Selenium-Y");
$sel->type_ok("name", "Selenium-test");
$sel->click_ok("update-group");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: Selenium-test");
$sel->is_text_present_ok("The name was changed to 'Selenium-test'");
# Delete the Selenium-test group.
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("//a[\@href='editgroups.cgi?action=del&group=${group_id}']");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_like(qr/^Delete group/);
$sel->is_text_present_ok("Do you really want to delete this group?");
$sel->is_element_present_ok("removebugs");
$sel->value_is("removebugs", "off");
$sel->is_text_present_ok("Remove all bugs from this group restriction for me");
$sel->is_element_present_ok("unbind");
$sel->value_is("unbind", "off");
$sel->is_text_present_ok("remove these controls");
$sel->click_ok("delete");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Cannot Delete Group");
my $error_msg = trim($sel->get_text("error_msg"));
ok($error_msg =~ /^The Selenium-test group cannot be deleted/, "Group is in use - not deletable");
$sel->go_back_ok();
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->check("removebugs");
$sel->check("unbind");
$sel->click_ok("delete");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Group Deleted");
$sel->is_text_present_ok("The group Selenium-test has been deleted.");
# No more bugs listed in the saved search as the bug group is gone.
$sel->click_ok("link=Selenium bugs");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: Selenium bugs");
$sel->is_text_present_ok("Zarro Boogs found");
$sel->click_ok("forget_search");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Search is gone");
$sel->is_text_present_ok("OK, the Selenium bugs search is gone.");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Create keywords. Do some cleanup first if necessary.
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Keywords");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Select keyword");
# If keywords already exist, delete them to not disturb the test.
my $page = $sel->get_body_text();
my @keywords = $page =~ m/(key-selenium-\w+)/gi;
foreach my $keyword (@keywords) {
my $url = $sel->get_attribute("link=$keyword\@href");
$url =~ s/action=edit/action=del/;
$sel->click_ok("//a[\@href='$url']");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Delete Keyword");
$sel->click_ok("delete");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Keyword Deleted");
}
# Now let's create our first keyword.
go_to_admin($sel);
$sel->click_ok("link=Keywords");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Select keyword");
$sel->click_ok("link=Add a new keyword");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Add keyword");
$sel->type_ok("name", "key-selenium-kone");
$sel->type_ok("description", "Hopefully an ice cream");
$sel->click_ok("create");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("New Keyword Created");
# Try create the same keyword, to check validators.
$sel->click_ok("link=Add a new keyword");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Add keyword");
$sel->type_ok("name", "key-selenium-kone");
$sel->type_ok("description", "FIX ME!");
$sel->click_ok("create");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Keyword Already Exists");
my $error_msg = trim($sel->get_text("error_msg"));
ok($error_msg eq 'A keyword with the name key-selenium-kone already exists.', 'Already created keyword');
$sel->go_back_ok();
$sel->wait_for_page_to_load(WAIT_TIME);
# Create a second keyword.
$sel->type_ok("name", "key-selenium-ktwo");
$sel->type_ok("description", "FIX ME!");
$sel->click_ok("create");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("New Keyword Created");
# Again test validators.
$sel->click_ok("link=key-selenium-ktwo");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit keyword");
$sel->type_ok("name", "key-selenium-kone");
$sel->type_ok("description", "the second keyword");
$sel->click_ok("update");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Keyword Already Exists");
$error_msg = trim($sel->get_text("error_msg"));
ok($error_msg eq 'A keyword with the name key-selenium-kone already exists.', 'Already created keyword');
$sel->go_back_ok();
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit keyword");
$sel->type_ok("name", "key-selenium-ktwo");
$sel->click_ok("update");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Keyword Updated");
# Add keywords to bugs
file_bug_in_product($sel, "TestProduct");
$sel->select_ok("component", "TestComponent");
$sel->type_ok("keywords", "key-selenium-kone");
my $bug_summary = "It's a beautiful day";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "This bug is to test keywords");
my $bug1_id = create_bug($sel, $bug_summary);
file_bug_in_product($sel, "TestProduct");
$sel->select_ok("component", "TestComponent");
$sel->type_ok("keywords", "key-selenium-kone, key-selenium-ktwo");
my $bug_summary2 = "Radio gaga";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", "This bug is also to test keywords, like bug $bug1_id");
my $bug2_id = create_bug($sel, $bug_summary2);
# Now make sure these bugs correctly appear in buglists.
open_advanced_search_page($sel);
$sel->remove_all_selections("product");
$sel->remove_all_selections("bug_status");
$sel->type_ok("keywords", "key-selenium-kone");
$sel->click_ok("Search");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("2 bugs found");
$sel->click_ok("link=Search");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Search for bugs");
$sel->remove_all_selections("product");
$sel->remove_all_selections("bug_status");
# Try with a different case than the one in the DB.
$sel->type_ok("keywords", "key-selenium-ktWO");
$sel->click_ok("Search");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("One bug found");
$sel->click_ok("link=Search");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Search for bugs");
$sel->remove_all_selections("product");
$sel->remove_all_selections("bug_status");
# Substrings also work for keywords.
$sel->type_ok("keywords", "selenium");
$sel->click_ok("Search");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("2 bugs found");
# Make sure describekeywords.cgi works as expected.
$sel->click_ok("link=$bug_summary");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
$sel->click_ok("link=Keywords:");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bugzilla Keyword Descriptions");
$sel->is_text_present_ok("key-selenium-kone");
$sel->is_text_present_ok("Hopefully an ice cream");
$sel->is_text_present_ok("key-selenium-ktwo");
$sel->is_text_present_ok("the second keyword");
$sel->click_ok('//a[@href="buglist.cgi?keywords=key-selenium-kone"]');
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_element_present_ok("link=$bug1_id");
$sel->is_element_present_ok("link=$bug2_id");
$sel->is_text_present_ok("2 bugs found");
$sel->go_back_ok();
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->click_ok('//a[@href="buglist.cgi?keywords=key-selenium-ktwo"]');
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_element_present_ok("link=$bug2_id");
$sel->is_text_present_ok("One bug found");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# FIXME - At some point, this trivial script should be merged with test_create_user_accounts.t.
# Either that or we should improve this script a lot.
# Try to log in to Bugzilla using an invalid account. To be sure that the login form
# is triggered, we try to file a new bug.
go_to_home($sel, $config);
$sel->click_ok("link=New");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Log in to Bugzilla");
# The login and password are hardcoded here, because this account doesn't exist.
$sel->type_ok("Bugzilla_login", 'guest@foo.com');
$sel->type_ok("Bugzilla_password", 'foo-bar-baz');
$sel->click_ok("log_in");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Invalid Login Or Password");
$sel->is_text_present_ok("The login or password you entered is not valid.");
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# 1st step: turn on usetargetmilestone and letsubmitterchoosemilestone.
log_in($sel, $config, 'admin');
set_parameters($sel, {'Bug Fields' => {'usetargetmilestone-on' => undef},
'Bug Change Policies' => {'letsubmitterchoosemilestone-on' => undef},
}
);
# 2nd step: Add the milestone "2.0" (with sortkey = 10) to the TestProduct product.
edit_product($sel, "TestProduct");
$sel->click_ok("link=Edit milestones:", undef, "Go to the Edit milestones page");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Select milestone of product 'TestProduct'", "Display milestones");
$sel->click_ok("link=Add", undef, "Go add a new milestone");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Add Milestone to Product 'TestProduct'", "Enter new milestone");
$sel->type_ok("milestone", "2.0", "Set its name to 2.0");
$sel->type_ok("sortkey", "10", "Set its sortkey to 10");
$sel->click_ok("create", undef, "Submit data");
$sel->wait_for_page_to_load(WAIT_TIME);
# If the milestone already exists, that's not a big deal. So no special action
# is required in this case.
$sel->title_is("Milestone Created", "Milestone Created");
# 3rd step: file a new bug, leaving the milestone alone (should fall back to the default one).
file_bug_in_product($sel, "TestProduct");
$sel->selected_label_is("component", "TestComponent", "Component already selected (no other component defined)");
$sel->selected_label_is("target_milestone", "---", "Default milestone selected");
$sel->selected_label_is("version", "unspecified", "Version already selected (no other version defined)");
my $bug_summary = "Target Milestone left to default";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "Created by Selenium to test 'musthavemilestoneonaccept'");
my $bug1_id = create_bug($sel, $bug_summary);
# 4th step: edit the bug
go_to_bug($sel, $bug1_id);
$sel->select_ok("bug_status", "label=IN_PROGRESS", "Change bug status to IN_PROGRESS");
$sel->select_ok("target_milestone", "label=2.0", "Select a non-default milestone");
edit_bug($sel, $bug1_id, $bug_summary);
# 5th step: create another bug.
file_bug_in_product($sel, "TestProduct");
$sel->select_ok("target_milestone", "label=2.0", "Set the milestone to 2.0");
$sel->selected_label_is("component", "TestComponent", "Component already selected (no other component defined)");
$sel->selected_label_is("version", "unspecified", "Version already selected (no other version defined)");
my $bug_summary2 = "Target Milestone set to non-default";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", "Created by Selenium to test milestone support");
my $bug2_id = create_bug($sel, $bug_summary2);
# 6th step: edit the bug
$sel->select_ok("bug_status", "label=IN_PROGRESS");
edit_bug($sel, $bug2_id, $bug_summary2);
# 7th step: test validation methods for milestones.
go_to_admin($sel);
$sel->click_ok("link=milestones");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit milestones for which product?");
$sel->click_ok("link=TestProduct");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Select milestone of product 'TestProduct'");
$sel->click_ok("link=2.0");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Milestone '2.0' of product 'TestProduct'");
$sel->type_ok("milestone", "1.0");
$sel->value_is("milestone", "1.0");
$sel->click_ok("update");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Milestone Updated");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Add Milestone to Product 'TestProduct'");
$sel->type_ok("milestone", "1.5");
$sel->value_is("milestone", "1.5");
$sel->type_ok("sortkey", "99999999999999999");
$sel->value_is("sortkey", "99999999999999999");
$sel->click_ok("create");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Invalid Milestone Sortkey");
my $error_msg = trim($sel->get_text("error_msg"));
ok($error_msg =~ /^The sortkey '99999999999999999' is not in the range/, "Invalid sortkey");
$sel->go_back_ok();
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->type_ok("sortkey", "-polu7A");
$sel->value_is("sortkey", "-polu7A");
$sel->click_ok("create");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Invalid Milestone Sortkey");
$error_msg = trim($sel->get_text("error_msg"));
ok($error_msg =~ /^The sortkey '-polu7A' is not in the range/, "Invalid sortkey");
$sel->go_back_ok();
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->click_ok("link='TestProduct'");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Select milestone of product 'TestProduct'");
$sel->click_ok("link=Delete");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Delete Milestone of Product 'TestProduct'");
$sel->is_text_present_ok("When you delete this milestone", undef, "Warn the user about bugs being affected");
$sel->click_ok("delete");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Milestone Deleted");
# 8th step: make sure the (now deleted) milestone of the bug has fallen back to the default milestone.
go_to_bug($sel, $bug1_id);
$sel->is_text_present_ok('regexp:Target Milestone:\W+---', undef, "Milestone has fallen back to the default milestone");
# 9th step: file another bug.
file_bug_in_product($sel, "TestProduct");
$sel->selected_label_is("target_milestone", "---", "Default milestone selected");
$sel->selected_label_is("component", "TestComponent");
my $bug_summary3 = "Only one Target Milestone available";
$sel->type_ok("short_desc", $bug_summary3);
$sel->type_ok("comment", "Created by Selenium to test milestone support");
my $bug3_id = create_bug($sel, $bug_summary3);
$sel->select_ok("bug_status", "label=IN_PROGRESS");
edit_bug($sel, $bug3_id, $bug_summary3);
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
set_parameters($sel, {"Administrative Policies" => {"allowuserdeletion-on" => undef},
"User Authentication" => {"createemailregexp" => {type => "text", value => '.*'},
"emailsuffix" => {type => "text", value => ''}} });
# Set the password complexity to MIXED LETTERS.
# Password must contain at least one UPPER and one lowercase letter.
my @invalid_mixed_letter = qw(lowercase UPPERCASE 1234567890 123lowercase
123UPPERCASE !@%&^lower !@&^UPPER);
check_passwords($sel, 'mixed_letters', \@invalid_mixed_letter, ['PaSSwOrd', '%9rT#j22S']);
# Set the password complexity to LETTERS AND NUMBERS.
# Passwords must contain at least one UPPER and one lower case letter and a number.
my @invalid_letter_number = (@invalid_mixed_letter, qw(lowerUPPER 123!@%^$));
check_passwords($sel, 'letters_numbers', \@invalid_letter_number, ['-UniCode6.3', 'UNO54sun']);
# Set the password complexity to LETTERS, NUMBERS AND SPECIAL CHARACTERS.
# Passwords must contain at least one letter, a number and a special character.
my @invalid_letter_number_splchar = (qw(!@%^&~* lowerUPPER123), @invalid_letter_number);
check_passwords($sel, 'letters_numbers_specialchars', \@invalid_letter_number_splchar, ['@gu731', 'HU%m70?']);
# Set the password complexity to No Constraints.
check_passwords($sel, 'no_constraints', ['12xY!', 'aaaaa'], ['aaaaaaaa', '>F12Xy?']);
logout($sel);
sub check_passwords {
my ($sel, $param, $invalid_passwords, $valid_passwords) = @_;
set_parameters($sel, { "User Authentication" => {"password_complexity" => {type => "select", value => $param}} });
my $new_user = 'selenium-' . random_string(10) . '@bugzilla.org';
go_to_admin($sel);
$sel->click_ok("link=Users");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Search users');
$sel->click_ok('link=add a new user');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Add user');
$sel->type_ok('login', $new_user);
foreach my $password (@$invalid_passwords) {
$sel->type_ok('password', $password, 'Enter password');
$sel->click_ok('add');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
if ($param eq 'no_constraints') {
$sel->title_is('Password Too Short');
}
else {
$sel->title_is('Password Fails Requirements');
}
my $error_msg = trim($sel->get_text("error_msg"));
if ($param eq 'mixed_letters') {
ok($error_msg =~ /UPPERCASE letter.*lowercase letter/,
"Mixed letter password fails requirement: $password");
}
elsif ($param eq 'letters_numbers') {
ok($error_msg =~ /UPPERCASE letter.*lowercase letter.*digit/,
"Letter & Number password fails requirement: $password");
}
elsif ($param eq 'letters_numbers_specialchars') {
ok($error_msg =~ /letter.*special character.*digit/,
"Letter, Number & Special Character password fails requirement: $password");
}
else {
ok($error_msg =~ /The password must be at least \d+ characters long/,
"Password Too Short: $password");
}
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
}
my $created = 0;
foreach my $password (@$valid_passwords) {
$sel->type_ok('password', $password, 'Enter password');
$sel->click_ok($created ? 'update' : 'add');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is($created ? "User $new_user updated" : "Edit user $new_user");
my $msg = trim($sel->get_text('message'));
if ($created++) {
ok($msg =~ /A new password has been set/, 'Account updated');
}
else {
ok($msg =~ /The user account $new_user has been created successfully/, 'Account created');
}
}
return unless $created;
$sel->click_ok('delete');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Confirm deletion of user $new_user");
$sel->click_ok('delete');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("User $new_user deleted");
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
# We have to upload files from the local computer. This requires
# chrome privileges.
my ($sel, $config) = get_selenium(CHROME_MODE);
# set the insidergroup parameter to the admin group, and make sure
# we can view and delete attachments.
log_in($sel, $config, 'admin');
set_parameters($sel, { "Group Security" => {"insidergroup" => {type => "select", value => "admin"}},
"Attachments" => {"allow_attachment_display-on" => undef,
"allow_attachment_deletion-on" => undef}
});
# First create a new bug with a private attachment.
file_bug_in_product($sel, "TestProduct");
my $bug_summary = "Some comments are private";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "and some attachments too, like this one.");
$sel->check_ok("comment_is_private");
$sel->click_ok('//input[@value="Add an attachment"]');
$sel->type_ok("data", $config->{attachment_file});
$sel->type_ok("description", "private attachment, v1");
$sel->check_ok("ispatch");
my $bug1_id = create_bug($sel, $bug_summary);
$sel->is_text_present_ok("private attachment, v1 (");
$sel->is_text_present_ok("and some attachments too, like this one.");
$sel->is_checked_ok('//a[@id="comment_link_0"]/../..//div//input[@type="checkbox"]');
# Now attach a public patch to the existing bug.
$sel->click_ok("link=Add an attachment");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create New Attachment for Bug #$bug1_id");
$sel->type_ok("data", $config->{attachment_file});
$sel->type_ok("description", "public attachment, v2");
$sel->check_ok("ispatch");
# The existing attachment name must be displayed, to mark it as obsolete.
$sel->is_text_present_ok("private attachment, v1");
$sel->type_ok("comment", "this patch is public. Everyone can see it.");
$sel->value_is("isprivate", "off");
edit_bug($sel, $bug1_id, $bug_summary, {id => "create"});
# We need to store the attachment ID.
$sel->is_text_present_ok("public attachment, v2");
my $alink = $sel->get_attribute('//a[@title="public attachment, v2"]@href');
$alink =~ /id=(\d+)/;
my $attachment1_id = $1;
$sel->is_text_present_ok("this patch is public. Everyone can see it.");
ok(!$sel->is_checked('//a[@id="comment_link_1"]/../..//div//input[@type="checkbox"]'), "Public attachment is visible");
logout($sel);
# A logged out user cannot see the private attachment, only the public one.
# Same for a user with no privs.
foreach my $user ('', 'unprivileged') {
log_in($sel, $config, $user) if $user;
go_to_bug($sel, $bug1_id);
ok(!$sel->is_text_present("private attachment, v1"), "Private attachment not visible");
$sel->is_text_present_ok("public attachment, v2");
ok(!$sel->is_text_present("and some attachments too, like this one"), "Private comment not visible");
$sel->is_text_present_ok("this patch is public. Everyone can see it.");
}
# A powerless user can comment on attachments he doesn't own.
$sel->click_ok('//a[@href="attachment.cgi?id=' . $attachment1_id . '&action=edit"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/Attachment $attachment1_id Details for Bug $bug1_id/);
$sel->is_text_present_ok("created by admin");
$sel->type_ok("comment", "This attachment is not mine.");
edit_bug($sel, $bug1_id, $bug_summary, {id => "update"});
$sel->is_text_present_ok("This attachment is not mine");
# Powerless users will always be able to view their own attachments, even
# when those are marked private by a member of the insider group.
$sel->click_ok("link=Add an attachment");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create New Attachment for Bug #$bug1_id");
$sel->type_ok("data", $config->{attachment_file});
$sel->check_ok("ispatch");
# The user doesn't have editbugs privs.
ok(!$sel->is_text_present("Check each existing attachment made obsolete by your new attachment"), "No attachments can be marked as obsolete");
$sel->type_ok("description", "My patch, which I should see, always");
$sel->type_ok("comment", "This is my patch!");
edit_bug($sel, $bug1_id, $bug_summary, {id => "create"});
$sel->is_text_present_ok("My patch, which I should see, always (");
$alink = $sel->get_attribute('//a[@title="My patch, which I should see, always"]@href');
$alink =~ /id=(\d+)/;
my $attachment2_id = $1;
$sel->is_text_present_ok("This is my patch!");
logout($sel);
# Let the admin mark the powerless user's attachment as private.
log_in($sel, $config, 'admin');
go_to_bug($sel, $bug1_id);
$sel->click_ok('//a[@href="attachment.cgi?id=' . $attachment2_id . '&action=edit"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/Attachment $attachment2_id Details for Bug $bug1_id/);
$sel->check_ok("isprivate");
$sel->type_ok("comment", "Making the powerless user's patch private.");
edit_bug($sel, $bug1_id, $bug_summary, {id => "update"});
$sel->is_text_present_ok("My patch, which I should see, always (");
$sel->is_checked_ok('//a[@id="comment_link_4"]/../..//div//input[@type="checkbox"]');
$sel->is_text_present_ok("Making the powerless user's patch private.");
logout($sel);
# A logged out user cannot see private attachments.
go_to_bug($sel, $bug1_id);
ok(!$sel->is_text_present("private attachment, v1"), "Private attachment not visible to logged out users");
ok(!$sel->is_text_present("My patch, which I should see, always ("), "Private attachment not visible to logged out users");
$sel->is_text_present_ok("This is my patch!");
ok(!$sel->is_text_present("Making the powerless user's patch private"), "Private comment not visible to logged out users");
# A powerless user can only see private attachments he owns.
log_in($sel, $config, 'unprivileged');
go_to_bug($sel, $bug1_id);
$sel->is_text_present_ok("My patch, which I should see, always (");
$sel->click_ok("link=My patch, which I should see, always");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
# No title displayed while viewing an attachment.
$sel->title_is("");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
logout($sel);
# Admins can delete attachments.
log_in($sel, $config, 'admin');
go_to_bug($sel, $bug1_id);
$sel->click_ok('//a[@href="attachment.cgi?id=' . $attachment2_id . '&action=edit"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/Attachment $attachment2_id Details for Bug $bug1_id/);
$sel->click_ok("link=Delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Attachment $attachment2_id of Bug $bug1_id");
$sel->is_text_present_ok("Do you really want to delete this attachment?");
$sel->type_ok("reason", "deleted by Selenium");
edit_bug_and_return($sel, $bug1_id, $bug_summary, {id => "delete"});
$sel->is_text_present_ok("deleted by Selenium");
$sel->click_ok("link=attachment $attachment2_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Attachment Removed");
$sel->is_text_present_ok("The attachment you are attempting to access has been removed");
set_parameters($sel, {
"Group Security" => {"insidergroup" => { type => "select",
value => "QA-Selenium-TEST" }},
});
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# First make sure the 'My QA query' saved search is gone.
log_in($sel, $config, 'admin');
if ($sel->is_text_present("My QA query")) {
$sel->click_ok("link=My QA query");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: My QA query");
$sel->click_ok("forget_search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search is gone");
$sel->is_text_present_ok("OK, the My QA query search is gone.");
}
# Enable the QA contact field and file a new bug restricted to the 'Master' group
# with a powerless user as the QA contact. He should only be able to access the
# bug if the QA contact field is enabled, else he looses this privilege.
set_parameters($sel, { "Bug Fields" => {"useqacontact-on" => undef} });
file_bug_in_product($sel, 'TestProduct');
$sel->type_ok("qa_contact", $config->{unprivileged_user_login}, "Set the powerless user as QA contact");
my $bug_summary = "Test for QA contact";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "This is a test to check QA contact privs.");
$sel->check_ok('//input[@name="groups" and @value="Master"]');
my $bug1_id = create_bug($sel, $bug_summary);
# Create a saved search querying for all bugs with the powerless user
# as QA contact.
open_advanced_search_page($sel);
$sel->remove_all_selections_ok("product");
$sel->add_selection_ok("product", "TestProduct");
$sel->remove_all_selections("bug_status");
$sel->select_ok("f1", "label=QA Contact");
$sel->select_ok("o1", "label=is equal to");
$sel->type_ok("v1", $config->{unprivileged_user_login}, "Look for the powerless user as QA contact");
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id is on the list");
$sel->is_text_present_ok("Test for QA contact");
$sel->type_ok("save_newqueryname", "My QA query");
$sel->click_ok("remember");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search created");
my $text = trim($sel->get_text("message"));
ok($text =~ /OK, you have a new search named My QA query/, "New saved search 'My QA query'");
$sel->click_ok("link=My QA query");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: My QA query");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id is on the list");
$sel->is_text_present_ok("Test for QA contact");
# The saved search should still work, even with the QA contact field disabled.
# ("work" doesn't mean you should still see all bugs, depending on your role
# and privs!)
set_parameters($sel, { "Bug Fields" => {"useqacontact-off" => undef} });
$sel->click_ok("link=My QA query");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: My QA query");
$sel->is_text_present_ok("One bug found");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id is on the list");
$sel->click_ok("link=$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
# The 'QA Contact' label must not be displayed.
ok(!$sel->is_text_present("QA Contact"), "The QA Contact label is not present");
logout($sel);
# You cannot access the bug when being logged out, as it's restricted
# to the Master group.
$sel->type_ok("quicksearch_top", $bug1_id);
$sel->click_ok("find_top");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug Access Denied");
$sel->is_text_present_ok("You are not authorized to access bug");
# You are still not allowed to access the bug when logged in as the
# powerless user, as the QA contact field is disabled.
# Don't use it log_in() as we want to follow this specific link.
$sel->click_ok("link=log in to an account", undef, "Log in");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Log in to Bugzilla");
$sel->is_text_present_ok("Bugzilla needs a legitimate login and password to continue");
$sel->type_ok("Bugzilla_login", $config->{unprivileged_user_login}, "Enter login name");
$sel->type_ok("Bugzilla_password", $config->{unprivileged_user_passwd}, "Enter password");
$sel->click_ok("log_in", undef, "Submit credentials");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug Access Denied");
$sel->is_text_present_ok("You are not authorized to access bug");
logout($sel);
# Re-enable the QA contact field.
log_in($sel, $config, 'admin');
set_parameters($sel, { "Bug Fields" => {"useqacontact-on" => undef} });
logout($sel);
# Log in as the powerless user. As the QA contact field is enabled again,
# you can now access the restricted bug.
log_in($sel, $config, 'unprivileged');
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->select_ok("state_addselfcc", "value=never");
$sel->click_ok("update");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
open_advanced_search_page($sel);
$sel->remove_all_selections_ok("product");
$sel->add_selection_ok("product", "TestProduct");
$sel->remove_all_selections_ok("bug_status");
$sel->select_ok("f1", "label=QA Contact");
$sel->select_ok("o1", "label=is equal to");
$sel->type_ok("v1", $config->{unprivileged_user_login}, "Look for the powerless user as QA contact");
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("One bug found");
$sel->is_element_present_ok("b$bug1_id", undef, "Bug $bug1_id is on the list");
$sel->is_text_present_ok("Test for QA contact");
$sel->click_ok("link=$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/$bug1_id /);
$sel->click_ok("bz_qa_contact_edit_action");
$sel->value_is("qa_contact", $config->{unprivileged_user_login}, "The powerless user is the current QA contact");
$sel->check_ok("set_default_qa_contact");
edit_bug($sel, $bug1_id, $bug_summary);
# The user is no longer the QA contact, and he has no other role
# with the bug. He can no longer see it.
$sel->is_text_present_ok("(list of e-mails not available)");
$sel->click_ok("link=$bug1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug Access Denied");
logout($sel);
$sel->stop();
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Turn on 'requirelogin'.
log_in($sel, $config, 'admin');
set_parameters($sel, { "User Authentication" => {"requirelogin-on" => undef} });
logout($sel);
# We try to access each page. None of the ones listed below should
# let you view it without being logged in.
my @pages = qw(admin attachment buglist chart colchange describecomponents
describekeywords duplicates editclassifications editcomponents
editfields editflagtypes editgroups editkeywords editmilestones
editparams editproducts editsettings editusers editvalues
editversions editwhines editworkflow enter_bug page post_bug
process_bug query quips report reports request sanitycheck
search_plugin show_activity show_bug showdependencygraph
showdependencytree summarize_time userprefs votes);
foreach my $page (@pages) {
$sel->open_ok("/$config->{bugzilla_installation}/${page}.cgi");
if ($page ne 'votes' || $config->{test_extensions}) {
$sel->title_is("Log in to Bugzilla");
}
else {
$sel->title_is("Extension Disabled");
}
}
# Those have parameters passed to the page, so we put them here separately.
@pages = ("query.cgi?format=report-table", "query.cgi?format=report-graph",
"votes.cgi?action=show_user", "votes.cgi?action=show_bug");
foreach my $page (@pages) {
$sel->open_ok("/$config->{bugzilla_installation}/$page");
if ($page !~ /^votes/ || $config->{test_extensions}) {
$sel->title_is("Log in to Bugzilla");
}
else {
$sel->title_is("Extension Disabled");
}
}
# These pages should still be accessible.
@pages = ("config.cgi", "createaccount.cgi", "index.cgi", "relogin.cgi",
"token.cgi?a=reqpw&loginname=" . $config->{unprivileged_user_login});
foreach my $page (@pages) {
$sel->open_ok("/$config->{bugzilla_installation}/$page");
$sel->title_isnt("Log in to Bugzilla");
}
# Turn off 'requirelogin'.
log_in($sel, $config, 'admin');
set_parameters($sel, { "User Authentication" => {"requirelogin-off" => undef} });
logout($sel);
# Make sure we can access random pages again.
$sel->click_ok("link=Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_isnt("Log in to Bugzilla");
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Sanity Check", undef, "Go to Sanity Check (no parameter)");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Sanity Check", "Display sanitycheck.cgi");
$sel->is_text_present_ok("Sanity check completed.", undef, "Page displayed correctly");
my @args = qw(rebuildvotecache createmissinggroupcontrolmapentries repair_creation_date
repair_bugs_fulltext remove_invalid_bug_references repair_bugs_fulltext
remove_invalid_attach_references remove_old_whine_targets rescanallBugMail);
foreach my $arg (@args) {
$sel->open_ok("/$config->{bugzilla_installation}/sanitycheck.cgi?$arg=1");
$sel->title_is("Suspicious Action", "Calling sanitycheck.cgi with no token triggers a confirmation page");
$sel->click_ok("confirm", "Confirm the action");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Sanity Check", "Calling sanitycheck.cgi with $arg=1");
if ($arg eq 'rescanallBugMail') {
# sanitycheck.cgi always stops after looking for unsent bugmail. So we cannot rely on
# "Sanity check completed." to determine if an error has been thrown or not.
$sel->is_text_present_ok("found with possibly unsent mail", undef, "Look for unsent bugmail");
ok(!$sel->is_text_present("Software error"), "No error thrown");
}
else {
$sel->is_text_present_ok("Sanity check completed.", undef, "Page displayed correctly");
}
}
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# If a saved search named 'SavedSearchTEST1' exists, remove it.
log_in($sel, $config, 'QA_Selenium_TEST');
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Saved Searches");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
if($sel->is_text_present("SavedSearchTEST1")) {
# There is no other way to identify this link (as they are all named "Forget").
$sel->click_ok('//a[contains(@href,"buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=SavedSearchTEST1")]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search is gone");
$sel->is_text_present_ok("OK, the SavedSearchTEST1 search is gone.");
}
# Create a new saved search.
open_advanced_search_page($sel);
$sel->type_ok("short_desc", "test search");
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->type_ok("save_newqueryname", "SavedSearchTEST1");
$sel->click_ok("remember");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search created");
my $text = trim($sel->get_text("message"));
ok($text =~ /OK, you have a new search named SavedSearchTEST1./, "New search named SavedSearchTEST1 has been created");
$sel->click_ok("link=SavedSearchTEST1");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: SavedSearchTEST1");
# Remove the saved search from the page footer. It should no longer be displayed there.
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Saved Searches");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
$sel->is_text_present_ok("SavedSearchTEST1");
$sel->uncheck_ok('//input[@type="checkbox" and @alt="SavedSearchTEST1"]');
# $sel->value_is("//input[\@type='checkbox' and \@alt='SavedSearchTEST1']", "off");
$sel->click_ok("update");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
$text = trim($sel->get_text("message"));
ok($text =~ /The changes to your saved searches have been saved./, "Saved searches changes have been saved");
# Modify the saved search. Said otherwise, we should still be able to save
# a new search with exactly the same name.
open_advanced_search_page($sel);
$sel->type_ok("short_desc", "bilboa");
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
# As we said, this saved search should no longer be displayed in the page footer.
ok(!$sel->is_text_present("SavedSearchTEST1"), "SavedSearchTEST1 is not present in the page footer");
$sel->type_ok("save_newqueryname", "SavedSearchTEST1");
$sel->click_ok("remember");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search updated");
$text = trim($sel->get_text("message"));
ok($text =~ /Your search named SavedSearchTEST1 has been updated./, "Saved searche SavedSearchTEST1 has been updated.");
# Make sure our new criteria has been saved (let's edit the saved search).
# As the saved search is no longer displayed in the footer, we have to go
# to the "Preferences" page to edit it.
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Saved Searches");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
$sel->is_text_present_ok("SavedSearchTEST1");
$sel->click_ok('//a[@href="buglist.cgi?cmdtype=dorem&remaction=run&namedcmd=SavedSearchTEST1"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: SavedSearchTEST1");
$sel->click_ok("edit_search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search for bugs");
$sel->value_is("short_desc", "bilboa");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->click_ok("forget_search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search is gone");
$text = trim($sel->get_text("message"));
ok($text =~ /OK, the SavedSearchTEST1 search is gone./, "The SavedSearchTEST1 search is gone.");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use QA::Util;
use Test::More "no_plan";
my ($sel, $config) = get_selenium();
# TODO: This test really needs improvement. There is by far much more stuff
# to test in this area.
# First, a very trivial search, which returns no result.
go_to_home($sel, $config);
open_advanced_search_page($sel);
$sel->type_ok("short_desc", "ois£jdfm#sd%fasd!fm", "Type a non-existent string in the bug summary field");
$sel->click_ok("Search");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("Zarro Boogs found");
# Display all available columns. Look for all bugs assigned to a user who doesn't exist.
$sel->open_ok("/$config->{bugzilla_installation}/buglist.cgi?quicksearch=%40xx45ft&columnlist=all");
$sel->title_is("Bug List");
$sel->is_text_present_ok("Zarro Boogs found");
# Now some real tests.
log_in($sel, $config, 'canconfirm');
file_bug_in_product($sel, "TestProduct");
my $bug_summary = "Update this summary with this bug ID";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "I'm supposed to appear in the coming buglist.");
my $bug1_id = create_bug($sel, $bug_summary);
$sel->click_ok("summary_edit_action");
$bug_summary .= ": my ID is $bug1_id";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "Updating bug summary....");
edit_bug($sel, $bug1_id, $bug_summary);
# Test pronoun substitution.
open_advanced_search_page($sel);
$sel->remove_all_selections("bug_status");
$sel->remove_all_selections("resolution");
$sel->type_ok("short_desc", "my ID is $bug1_id");
$sel->select_ok("f1", "label=Commenter");
$sel->select_ok("o1", "label=is equal to");
$sel->type_ok("v1", "%user%");
$sel->click_ok("add_button");
$sel->select_ok("f2", "label=Comment");
$sel->select_ok("o2", "label=contains the string");
$sel->type_ok("v2", "coming buglist");
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("One bug found");
$sel->is_text_present_ok("Update this summary with this bug ID: my ID is $bug1_id");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium(CHROME_MODE);
my $urlbase = $config->{bugzilla_installation};
my $admin_user = $config->{admin_user_login};
# Let's create a bug and attachment to play with.
log_in($sel, $config, 'admin');
file_bug_in_product($sel, "TestProduct");
my $bug_summary = "Security checks";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "This bug will be used to test security fixes.");
$sel->type_ok("data", $config->{attachment_file});
$sel->type_ok("description", "simple patch, v1");
$sel->click_ok("ispatch");
my $bug1_id = create_bug($sel, $bug_summary);
#######################################################################
# Security bug 38862.
#######################################################################
# No alternate host for attachments; cookies will be accessible.
set_parameters($sel, { "Attachments" => {"allow_attachment_display-on" => undef,
"reset-attachment_base" => undef} });
go_to_bug($sel, $bug1_id);
$sel->click_ok("link=Add an attachment");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Create New Attachment for Bug #$bug1_id");
$sel->type_ok("attach_text", "<html>\n<head>\n<title>I want your cookies</title>\n<head>\n" .
"<body>\n<script type='text/javascript'>document.write(document.cookie);</script>\n" .
"</body>\n</html>", "Writing text into the attachment textarea");
$sel->type_ok("description", "show my cookies");
edit_bug($sel, $bug1_id, $bug_summary, {id => "create"});
my $alink = $sel->get_attribute('//a[@title="show my cookies"]@href');
$alink =~ /id=(\d+)/;
my $attach1_id = $1;
$sel->click_ok("link=Attachment #$attach1_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/Attachment $attach1_id Details for Bug $bug1_id/);
$sel->click_ok("link=edit details");
$sel->type_ok("contenttypeentry", "text/html");
edit_bug($sel, $bug1_id, $bug_summary, {id => "update"});
$sel->click_ok("link=show my cookies");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("I want your cookies");
my @cookies = split(/[\s;]+/, $sel->get_body_text());
my $nb_cookies = scalar @cookies;
ok($nb_cookies, "Found $nb_cookies cookies:\n" . join("\n", @cookies));
ok(!$sel->is_cookie_present("Bugzilla_login"), "Bugzilla_login not accessible");
ok(!$sel->is_cookie_present("Bugzilla_logincookie"), "Bugzilla_logincookie not accessible");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
# Alternate host for attachments; no cookie should be accessible.
set_parameters($sel, { "Attachments" => {"attachment_base" => {type => "text",
value => "http://127.0.0.1/$urlbase"}} });
go_to_bug($sel, $bug1_id);
$sel->click_ok("link=show my cookies");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("I want your cookies");
@cookies = split(/[\s;]+/, $sel->get_body_text());
$nb_cookies = scalar @cookies;
ok(!$nb_cookies, "No cookies found");
ok(!$sel->is_cookie_present("Bugzilla_login"), "Bugzilla_login not accessible");
ok(!$sel->is_cookie_present("Bugzilla_logincookie"), "Bugzilla_logincookie not accessible");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
set_parameters($sel, { "Attachments" => {"reset-attachment_base" => undef} });
#######################################################################
# Security bug 472362.
#######################################################################
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
my $admin_cookie = $sel->get_value("token");
logout($sel);
log_in($sel, $config, 'editbugs');
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
my $editbugs_cookie = $sel->get_value("token");
# Using our own unused token is fine.
$sel->open_ok("/$urlbase/userprefs.cgi?dosave=1&display_quips=off&token=$editbugs_cookie");
$sel->title_is("General Preferences");
$sel->is_text_present_ok("The changes to your general preferences have been saved");
# Reusing a token must fail. They must all trigger the Suspicious Action warning.
my @args = ("", "token=", "token=i123x", "token=$admin_cookie", "token=$editbugs_cookie");
foreach my $arg (@args) {
$sel->open_ok("/$urlbase/userprefs.cgi?dosave=1&display_quips=off&$arg");
$sel->title_is("Suspicious Action");
if ($arg eq "token=$admin_cookie") {
$sel->is_text_present_ok("Generated by: admin <$admin_user>");
$sel->is_text_present_ok("This token has not been generated by you");
}
else {
$sel->is_text_present_ok("It looks like you didn't come from the right page");
}
}
logout($sel);
#######################################################################
# Security bug 529416.
#######################################################################
log_in($sel, $config, 'admin');
file_bug_in_product($sel, "TestProduct");
$sel->type_ok("alias", "secret_qa_bug_" . ($bug1_id + 1));
my $bug_summary2 = "Private QA Bug";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", "This private bug is used to test security fixes.");
$sel->type_ok("dependson", $bug1_id);
$sel->check_ok('//input[@name="groups" and @value="Master"]');
my $bug2_id = create_bug($sel, $bug_summary2);
go_to_bug($sel, $bug1_id);
$sel->is_text_present_ok("secret_qa_bug_$bug2_id");
logout($sel);
log_in($sel, $config, 'editbugs');
go_to_bug($sel, $bug1_id);
ok(!$sel->is_text_present("secret_qa_bug_$bug2_id"), "The alias 'secret_qa_bug_$bug2_id' is not visible for unauthorized users");
$sel->is_text_present_ok($bug2_id);
logout($sel);
go_to_bug($sel, $bug1_id);
ok(!$sel->is_text_present("secret_qa_bug_$bug2_id"), "The alias 'secret_qa_bug_$bug2_id' is not visible for logged out users");
$sel->is_text_present_ok($bug2_id);
#######################################################################
# Security bug 472206.
# Keep this test as the very last one as the File Saver will remain
# open till the end of the script. Selenium is currently* unable
# to interact with it and close it (* = 2.6.0).
#######################################################################
log_in($sel, $config, 'admin');
set_parameters($sel, { "Attachments" => {"allow_attachment_display-off" => undef} });
# Attachments are not viewable.
go_to_bug($sel, $bug1_id);
$sel->click_ok("link=Details");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/Attachment \d+ Details for Bug $bug1_id/);
$sel->is_text_present_ok("The attachment is not viewable in your browser due to security restrictions");
$sel->click_ok("link=View");
# Wait 1 second to give the browser a chance to display the attachment.
# Do not use wait_for_page_to_load_ok() as the File Saver will never go away.
sleep(1);
ok(!$sel->is_text_present('@@'), "Patch not displayed");
# Enable viewing attachments.
set_parameters($sel, { "Attachments" => {"allow_attachment_display-on" => undef} });
go_to_bug($sel, $bug1_id);
$sel->click_ok('link=simple patch, v1');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("");
$sel->is_text_present_ok('@@');
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/$bug1_id /);
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Set the querysharegroup param to be the canconfirm group.
log_in($sel, $config, 'admin');
set_parameters($sel, { "Group Security" => {"querysharegroup" => {type => "select", value => "canconfirm"}} });
# Create new saved search and call it 'Shared Selenium buglist'.
$sel->type_ok("quicksearch_top", ":TestProduct Selenium");
$sel->click_ok("find_top");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->type_ok("save_newqueryname", "Shared Selenium buglist");
$sel->click_ok("remember");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search created");
my $text = trim($sel->get_text("message"));
ok($text =~ /OK, you have a new search named Shared Selenium buglist./, "New search named 'Shared Selenium buglist' has been created");
# Retrieve the newly created saved search's internal ID and make sure it's displayed
# in the footer by default.
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Saved Searches");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
my $ssname = $sel->get_attribute('//input[@type="checkbox" and @alt="Shared Selenium buglist"]@name');
$ssname =~ /(?:link_in_footer_(\d+))/;
my $saved_search1_id = $1;
$sel->is_checked_ok("link_in_footer_$saved_search1_id");
# As an admin, the "Add to footer" checkbox must be displayed, but unchecked by default.
$sel->select_ok("share_$saved_search1_id", "label=canconfirm");
ok(!$sel->is_checked("force_$saved_search1_id"), "Shared search not displayed in other users' footer by default");
$sel->click_ok("force_$saved_search1_id");
$sel->click_ok("update");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
logout($sel);
# Log in as the "canconfirm" user. The search shared by the admin must appear
# in the footer.
log_in($sel, $config, 'canconfirm');
$sel->is_text_present_ok("Shared Selenium buglist");
$sel->click_ok("link=Shared Selenium buglist");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: Shared Selenium buglist");
# You cannot delete other users' saved searches.
ok(!$sel->is_element_present("forget_search"), "'Forget...' button not available");
# The name of the sharer must appear in the "Saved Searches" section.
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Saved Searches");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
$sel->is_text_present_ok($config->{admin_user_login});
# Remove the shared search from your footer.
$sel->is_checked_ok("link_in_footer_$saved_search1_id");
$sel->click_ok("link_in_footer_$saved_search1_id");
$sel->click_ok("update");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
# Go to a page where the query name is unlikely to appear in the main page.
$sel->click_ok("link=Permissions");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Permissions");
ok(!$sel->is_text_present("Shared Selenium buglist"), "Shared query no longer displayed in the footer");
# Create your own saved search, and share it with the canconfirm group.
$sel->type_ok("quicksearch_top", ":TestProduct sw:helpwanted");
$sel->click_ok("find_top");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->type_ok("save_newqueryname", "helpwanted");
$sel->click_ok("remember");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search created");
$text = trim($sel->get_text("message"));
ok($text =~ /OK, you have a new search named helpwanted./, "New search named helpwanted has been created");
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Saved Searches");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
$ssname = $sel->get_attribute('//input[@type="checkbox" and @alt="helpwanted"]@name');
$ssname =~ /(?:link_in_footer_(\d+))/;
my $saved_search2_id = $1;
# Our own saved searches are displayed in the footer by default.
$sel->is_checked_ok("link_in_footer_$saved_search2_id");
$sel->select_ok("share_$saved_search2_id", "label=canconfirm");
$sel->click_ok("update");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
logout($sel);
# Log in as admin again. The other user is not a blesser for the 'canconfirm'
# group, and so his shared search must not be displayed by default. But it
# must still be available and can be added to the footer, if desired.
log_in($sel, $config, 'admin');
ok(!$sel->is_text_present("helpwanted"), "No 'helpwanted' shared search displayed");
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Saved Searches");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
$sel->is_text_present_ok("helpwanted");
$sel->is_text_present_ok($config->{canconfirm_user_login});
ok(!$sel->is_checked("link_in_footer_$saved_search2_id"), "Shared query available but not displayed");
$sel->click_ok("link_in_footer_$saved_search2_id");
$sel->click_ok("update");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
# This query is now available from the footer.
$sel->click_ok("link=helpwanted");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: helpwanted");
# Remove the 'Shared Selenium buglist' query.
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Saved Searches");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
# There is no better way to identify the link
$sel->click_ok('//a[contains(@href,"buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=Shared%20Selenium%20buglist")]',
undef, "Deleting the 'Shared Selenium buglist' search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search is gone");
$text = trim($sel->get_text("message"));
ok($text =~ /OK, the Shared Selenium buglist search is gone./, "The 'Shared Selenium buglist' search is gone");
logout($sel);
# Make sure that the 'helpwanted' query is not shared with the QA_Selenium_TEST
# user as he doesn't belong to the 'canconfirm' group.
log_in($sel, $config, 'QA_Selenium_TEST');
ok(!$sel->is_text_present("helpwanted"), "The 'helpwanted' query is not displayed in the footer");
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Saved Searches");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
ok(!$sel->is_text_present("helpwanted"), "The 'helpwanted' query is not shared with this user");
logout($sel);
# Now remove the 'helpwanted' saved search.
log_in($sel, $config, 'canconfirm');
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Saved Searches");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Saved Searches");
ok(!$sel->is_text_present("Shared Selenium buglist"), "The 'Shared Selenium buglist' is no longer available");
$sel->click_ok('//a[contains(@href,"buglist.cgi?cmdtype=dorem&remaction=forget&namedcmd=helpwanted")]',
undef, "Deleting the 'helpwanted' search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search is gone");
$text = trim($sel->get_text("message"));
ok($text =~ /OK, the helpwanted search is gone./, "The 'helpwanted' search is gone");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
set_parameters($sel, { "Bug Fields" => {"useclassification-on" => undef} });
# Do not use file_bug_in_product() because our goal here is not to file
# a bug but to check what is present in the UI, and also to make sure
# that we get exactly the right page with the right information.
#
# The admin is not a member of the "QA‑Selenium‑TEST" group, and so
# cannot see the "QA‑Selenium‑TEST" product.
$sel->click_ok("link=New");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok("Select Classification");
my $full_text = trim($sel->get_body_text());
ok($full_text =~ /All: Show all products/, "The 'All' link is displayed");
$sel->click_ok("link=All");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok("Select Product");
ok(!$sel->is_text_present("QA-Selenium-TEST"), "The QA-Selenium-TEST product is not displayed");
logout($sel);
# Same steps, but for a member of the "QA‑Selenium‑TEST" group.
# The "QA‑Selenium‑TEST" product must be visible to him.
log_in($sel, $config, 'QA_Selenium_TEST');
$sel->click_ok("link=New");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok("Select Classification");
$sel->click_ok("link=All");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok("Select Product");
$sel->is_text_present_ok("QA-Selenium-TEST");
$sel->click_ok('//a[contains(@href, "product=QA-Selenium-TEST")]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Enter Bug: QA-Selenium-TEST");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
set_parameters($sel, { "General" => {shutdownhtml => {type => "text",
value => "I'm down (set by test_shutdown.t)" }
} });
# None of the following pages should be accessible when Bugzilla is down.
my @pages = qw(admin attachment buglist chart colchange config createaccount
describecomponents describekeywords duplicates
editclassifications editcomponents editfields editflagtypes
editgroups editkeywords editmilestones editproducts editsettings
editusers editvalues editversions editwhines editworkflow
enter_bug index page post_bug process_bug query quips relogin
report reports request sanitycheck search_plugin show_activity
show_bug showdependencygraph showdependencytree summarize_time
token userprefs votes xmlrpc);
foreach my $page (@pages) {
$sel->open_ok("/$config->{bugzilla_installation}/${page}.cgi");
$sel->title_is("Bugzilla is Down");
}
# Those have parameters passed to the page, so we put them here separately.
@pages = ("query.cgi?format=report-table", "query.cgi?format=report-graph",
"votes.cgi?action=show_user", "votes.cgi?action=show_bug");
foreach my $page (@pages) {
$sel->open_ok("/$config->{bugzilla_installation}/$page");
$sel->title_is("Bugzilla is Down");
}
# Clear 'shutdownhtml', to re-enable Bugzilla.
# At this point, the admin has been logged out. We cannot use log_in(),
# nor set_parameters(), due to shutdownhtml being active.
$sel->open_ok("/$config->{bugzilla_installation}/editparams.cgi");
$sel->title_is("Log in to Bugzilla");
$sel->type_ok("Bugzilla_login", $config->{admin_user_login}, "Enter admin login name");
$sel->type_ok("Bugzilla_password", $config->{admin_user_passwd}, "Enter admin password");
$sel->click_ok("log_in");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Configuration: Required Settings");
$sel->click_ok("link=General");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Configuration: General");
$sel->type_ok("shutdownhtml", "");
$sel->click_ok('//input[@type="submit" and @value="Save Changes"]', undef, "Save Changes");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Parameters Updated");
# Accessing index.cgi should work again now.
$sel->click_ok("link=Home");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugzilla Main Page");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
set_parameters($sel, {'Bug Fields' => {'usestatuswhiteboard-on' => undef}});
# Make sure the status whiteboard is displayed and add stuff to it.
file_bug_in_product($sel, "TestProduct");
$sel->select_ok("component", "TestComponent");
my $bug_summary = "white and black";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "This bug is to test the status whiteboard");
my $bug1_id = create_bug($sel, $bug_summary);
$sel->is_text_present_ok("Whiteboard:");
$sel->type_ok("status_whiteboard", "[msg from test_status_whiteboard.t: x77v]");
edit_bug($sel, $bug1_id, $bug_summary);
file_bug_in_product($sel, "TestProduct");
$sel->select_ok("component", "TestComponent");
my $bug_summary2 = "WTC";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", "bugzillation!");
my $bug2_id = create_bug($sel, $bug_summary2);
$sel->type_ok("status_whiteboard", "[msg from test_status_whiteboard.t: x77v]");
edit_bug($sel, $bug2_id, $bug_summary2);
# Now search these bugs above using data being in the status whiteboard,
# and save the query.
open_advanced_search_page($sel);
$sel->remove_all_selections_ok("product");
$sel->remove_all_selections_ok("bug_status");
$sel->type_ok("status_whiteboard", "x77v");
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("2 bugs found");
$sel->type_ok("save_newqueryname", "sw-x77v");
$sel->click_ok("remember");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search created");
my $text = trim($sel->get_text("message"));
ok($text =~ /you have a new search named sw-x77v/, 'Saved search correctly saved');
# Make sure the saved query works.
$sel->click_ok("link=sw-x77v");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: sw-x77v");
$sel->is_text_present_ok("2 bugs found");
# The status whiteboard should no longer be displayed in both the query
# and bug view pages (query.cgi and show_bug.cgi) when usestatuswhiteboard
# is off.
set_parameters($sel, {'Bug Fields' => {'usestatuswhiteboard-off' => undef}});
$sel->click_ok("link=Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search for bugs");
ok(!$sel->is_text_present("Whiteboard:"), "Whiteboard label no longer displayed in the search page");
go_to_bug($sel, $bug1_id);
ok(!$sel->is_text_present("Whiteboard:"), "Whiteboard label no longer displayed in the bug page");
# Queries based on the status whiteboard should still work when
# the parameter is off.
$sel->click_ok("link=sw-x77v");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: sw-x77v");
$sel->is_text_present_ok("2 bugs found");
# Turn on usestatuswhiteboard again as some other scripts may expect the status
# whiteboard to be available by default.
set_parameters($sel, {'Bug Fields' => {'usestatuswhiteboard-on' => undef}});
# Clear the status whiteboard and delete the saved search.
$sel->click_ok("link=sw-x77v");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: sw-x77v");
$sel->is_text_present_ok("2 bugs found");
$sel->click_ok("mass_change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->click_ok("check_all");
$sel->type_ok("status_whiteboard", "");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bugs processed");
$sel->click_ok("link=sw-x77v");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: sw-x77v");
$sel->is_text_present_ok("Zarro Boogs found");
$sel->click_ok("forget_search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search is gone");
$sel->is_text_present_ok("OK, the sw-x77v search is gone.");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
my $qa_user = $config->{QA_Selenium_TEST_user_login};
my $no_privs_user = $config->{unprivileged_user_login};
log_in($sel, $config, 'admin');
set_parameters($sel, { "Group Security" => {"strict_isolation-on" => undef} });
# Restrict the bug to the "Master" group, so that we can check that only
# allowed people can be CC'ed to the bug.
file_bug_in_product($sel, 'Another Product');
$sel->select_ok("component", "label=c2");
$sel->select_ok("version", "label=Another2");
my $bug_summary = "Test isolation";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "Unallowed users refused");
my $master_gid = $sel->get_attribute('//input[@type="checkbox" and @name="groups" and @value="Master"]@id');
$sel->check_ok($master_gid);
$master_gid =~ s/group_//;
my $bug1_id = create_bug($sel, $bug_summary);
# At that point, CANEDIT is off and so everybody can be CC'ed to the bug.
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", "$qa_user, $no_privs_user");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->click_ok("cc_edit_area_showhide");
$sel->add_selection_ok("cc", "label=$no_privs_user");
$sel->add_selection_ok("cc", "label=$qa_user");
$sel->check_ok("removecc");
edit_bug($sel, $bug1_id, $bug_summary);
# Now enable CANEDIT for the "Master" group. This will enable strict isolation
# for the product.
edit_product($sel, "Another Product");
$sel->click_ok("link=Edit Group Access Controls:");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Group Controls for Another Product");
$sel->check_ok("canedit_$master_gid");
$sel->click_ok("submit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Update group access controls for Another Product");
# Non-members can no longer be CC'ed to the bug.
go_to_bug($sel, $bug1_id);
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", $no_privs_user);
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Invalid User Group");
$sel->is_text_present_ok("User '$no_privs_user' is not able to edit the 'Another Product' Product");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", $qa_user);
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Invalid User Group");
$sel->is_text_present_ok("User '$qa_user' is not able to edit the 'Another Product' Product");
# Now set QA_Selenium_TEST user as a member of the Master group.
go_to_admin($sel);
$sel->click_ok("link=Users");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search users");
$sel->type_ok("matchstr", $qa_user);
$sel->select_ok("matchtype", "label=exact (find this user)");
$sel->click_ok("search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit user QA-Selenium-TEST <$qa_user>");
$sel->check_ok("group_$master_gid");
$sel->click_ok("update");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("User $qa_user updated");
# The QA_Selenium_TEST user can now be CC'ed to the bug.
go_to_bug($sel, $bug1_id);
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", $qa_user);
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->click_ok("cc_edit_area_showhide");
$sel->add_selection_ok("cc", "label=$qa_user");
$sel->check_ok("removecc");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
# The powerless user still cannot be CC'ed.
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", "$qa_user, $no_privs_user");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Invalid User Group");
$sel->is_text_present_ok("User '$no_privs_user' is not able to edit the 'Another Product' Product");
# Reset parameters back to defaults.
set_parameters($sel, { "Group Security" => {"strict_isolation-off" => undef} });
go_to_admin($sel);
$sel->click_ok("link=Users");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search users");
$sel->type_ok("matchstr", $qa_user);
$sel->select_ok("matchtype", "label=exact (find this user)");
$sel->click_ok("search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit user QA-Selenium-TEST <$qa_user>");
$sel->uncheck_ok("group_$master_gid");
$sel->click_ok("update");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("User $qa_user updated");
edit_product($sel, "Another Product");
$sel->click_ok("link=Edit Group Access Controls:");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Group Controls for Another Product");
$sel->uncheck_ok("canedit_$master_gid");
$sel->click_ok("submit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Update group access controls for Another Product");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Turn on the usevisibilitygroups param so that some users are invisible.
log_in($sel, $config, 'admin');
set_parameters($sel, { "Group Security" => {"usevisibilitygroups-on" => undef} });
# You can see all users from editusers.cgi, but once you leave this page,
# usual group visibility restrictions apply and the "powerless" user cannot
# be sudo'ed as he is in no group.
go_to_admin($sel);
$sel->click_ok("link=Users");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search users");
$sel->type_ok("matchstr", $config->{unprivileged_user_login});
$sel->select_ok("matchtype", "label=exact (find this user)");
$sel->click_ok("search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit user no-privs <$config->{unprivileged_user_login}>");
$sel->value_is("login", $config->{unprivileged_user_login});
$sel->click_ok("link=Impersonate this user");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Begin sudo session");
$sel->value_is("target_login", $config->{unprivileged_user_login});
$sel->type_ok("reason", "Selenium test about sudo sessions");
$sel->type_ok("password", $config->{admin_user_passwd}, "Enter admin password");
$sel->click_ok('//input[@value="Begin Session"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Match Failed");
my $error_msg = trim($sel->get_text("error_msg"));
ok($error_msg eq "$config->{unprivileged_user_login} does not exist or you are not allowed to see that user.",
"Cannot impersonate users you cannot see");
# Turn off the usevisibilitygroups param so that all users are visible again.
set_parameters($sel, { "Group Security" => {"usevisibilitygroups-off" => undef} });
# The "powerless" user can now be sudo'ed.
go_to_admin($sel);
$sel->click_ok("link=Users");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search users");
$sel->type_ok("matchstr", $config->{unprivileged_user_login});
$sel->select_ok("matchtype", "label=exact (find this user)");
$sel->click_ok("search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit user no-privs <$config->{unprivileged_user_login}>");
$sel->value_is("login", $config->{unprivileged_user_login});
$sel->click_ok("link=Impersonate this user");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Begin sudo session");
$sel->value_is("target_login", $config->{unprivileged_user_login});
$sel->type_ok("password", $config->{admin_user_passwd}, "Enter admin password");
$sel->click_ok('//input[@value="Begin Session"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Sudo session started");
my $text = trim($sel->get_text("message"));
ok($text =~ /The sudo session has been started/, "The sudo session has been started");
# Make sure this user is not an admin and has no privs at all, and that
# he cannot access editusers.cgi (despite the sudoer can).
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("General Preferences");
$sel->click_ok("link=Permissions");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Permissions");
$sel->is_text_present_ok("There are no permission bits set on your account");
# We access the page directly as there is no link pointing to it.
$sel->open_ok("/$config->{bugzilla_installation}/editusers.cgi");
$sel->title_is("Authorization Required");
$error_msg = trim($sel->get_text("error_msg"));
ok($error_msg =~ /^Sorry, you aren't a member of the 'editusers' group/, "Not a member of the editusers group");
$sel->click_ok("link=end session");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Sudo session complete");
$sel->is_text_present_ok("Your sudo session has ended");
# Try to access the sudo page directly, with no credentials.
$sel->open_ok("/$config->{bugzilla_installation}/relogin.cgi?action=begin-sudo&target_login=$config->{admin_user_login}");
$sel->title_is("Password Required");
# Now try to start a sudo session directly, with all required credentials.
$sel->open_ok("/$config->{bugzilla_installation}/relogin.cgi?action=begin-sudo&password=$config->{admin_user_passwd}&target_login=$config->{unprivileged_user_login}", undef, "Impersonate a user directly by providing all required data");
# A direct access to the page is supposed to have no Referer header set,
# which would trigger the "Untrusted Authentication Request" error, but
# due to the way Selenium works, the Referer header is set and the
# "Preparation Required" error is thrown instead. In any case, one of
# those two errors must be thrown.
my $title = $sel->get_title();
ok($title eq "Untrusted Authentication Request" || $title eq "Preparation Required", $title);
# Now try to sudo an admin, which is not allowed.
$sel->open_ok("/$config->{bugzilla_installation}/relogin.cgi?action=prepare-sudo&target_login=$config->{admin_user_login}");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Begin sudo session");
$sel->value_is("target_login", $config->{admin_user_login});
$sel->type_ok("reason", "Selenium hack");
$sel->type_ok("password", $config->{admin_user_passwd}, "Enter admin password");
$sel->click_ok('//input[@value="Begin Session"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("User Protected");
$error_msg = trim($sel->get_text("error_msg"));
ok($error_msg =~ /^The user $config->{admin_user_login} may not be impersonated by sudoers/, "Cannot impersonate administrators");
# Now try to sudo a non-existing user account, with no password.
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Begin sudo session");
# Starting with 5.0, the password field is a type=password and is marked
# "required". This means that we need to remove the required attribute from
# the input so that it can still be checked by the backend code.
my $script = q{
document.getElementById('password').removeAttribute('required');
};
$sel->run_script($script);
$sel->type_ok("target_login", 'foo@bar.com');
$sel->click_ok('//input[@value="Begin Session"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Password Required");
# Same as above, but with your password.
$sel->open_ok("/$config->{bugzilla_installation}/relogin.cgi?action=prepare-sudo&target_login=foo\@bar.com");
$sel->title_is("Begin sudo session");
$sel->value_is("target_login", 'foo@bar.com');
$sel->type_ok("password", $config->{admin_user_passwd}, "Enter admin password");
$sel->click_ok('//input[@value="Begin Session"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Match Failed");
$error_msg = trim($sel->get_text("error_msg"));
ok($error_msg eq 'foo@bar.com does not exist or you are not allowed to see that user.', "Cannot impersonate non-existing accounts");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
set_parameters($sel, { "Bug Fields" => {"usetargetmilestone-on" => undef} });
# Create a new milestone to the 'TestProduct' product.
edit_product($sel, "TestProduct");
$sel->click_ok("link=Edit milestones:");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Select milestone of product 'TestProduct'");
$sel->click_ok("link=Add");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Add Milestone to Product 'TestProduct'");
$sel->type_ok("milestone", "TM1");
$sel->type_ok("sortkey", "10");
$sel->click_ok("create");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Milestone Created");
# Edit the milestone of bugs.
file_bug_in_product($sel, "TestProduct");
$sel->select_ok("component", "TestComponent");
my $bug_summary = "stone and rock";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "This bug is to test milestones");
my $bug1_id = create_bug($sel, $bug_summary);
$sel->is_text_present_ok("Target Milestone:");
$sel->select_ok("target_milestone", "label=TM1");
edit_bug($sel, $bug1_id, $bug_summary);
# Query for bugs with the TM1 milestone.
open_advanced_search_page($sel);
$sel->is_text_present_ok("Target Milestone:");
$sel->remove_all_selections_ok("product");
$sel->add_selection_ok("product", "label=TestProduct");
$sel->add_selection_ok("target_milestone", "label=TM1");
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("One bug found");
$sel->type_ok("save_newqueryname", "selenium_m0");
$sel->click_ok("remember");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search created");
my $text = trim($sel->get_text("message"));
ok($text =~ /OK, you have a new search named selenium_m0./, "New search named selenium_m0 has been created");
# Turn off milestones and check that the milestone field no longer appears in bugs.
set_parameters($sel, { "Bug Fields" => {"usetargetmilestone-off" => undef} });
$sel->click_ok("link=Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search for bugs");
ok(!$sel->is_text_present("Target:"), "The target milestone field is no longer displayed in the search page");
go_to_bug($sel, $bug1_id);
ok(!$sel->is_text_present("Target Milestone:"), "The milestone field is no longer displayed in the bug page");
# The existing query must still work despite milestones are off now.
$sel->click_ok("link=selenium_m0");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List: selenium_m0");
$sel->is_text_present_ok("One bug found");
$sel->click_ok("forget_search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search is gone");
$text = trim($sel->get_text("message"));
ok($text =~ /OK, the selenium_m0 search is gone./, "The selenium_m0 search is gone");
# Re-enable the usetargetmilestone parameter and delete the created
# milestone from the Testproduct product.
set_parameters($sel, { "Bug Fields" => {"usetargetmilestone-on" => undef} });
edit_product($sel, "TestProduct");
$sel->click_ok("link=Edit milestones:");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Select milestone of product 'TestProduct'");
$sel->click_ok('//a[@href="editmilestones.cgi?action=del&product=TestProduct&milestone=TM1"]',
undef, "Deleting the TM1 milestone");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Milestone of Product 'TestProduct'");
$text = trim($sel->get_body_text());
ok($text =~ /There is 1 bug entered for this milestone/, "Warning displayed about 1 bug targetted to TM1");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Milestone Deleted");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Set the timetracking group to "editbugs", which is the default value for this parameter.
log_in($sel, $config, 'admin');
set_parameters($sel, { "Group Security" => {"timetrackinggroup" => {type => "select", value => "editbugs"}} });
# Add some Hours Worked to a bug so that we are sure at least one bug
# will be present in our buglist below.
file_bug_in_product($sel, "TestProduct");
$sel->select_ok("component", "TestComponent");
my $bug_summary = "Rocket science";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "Time flies");
my $bug1_id = create_bug($sel, $bug_summary);
$sel->type_ok("work_time", 2.6);
$sel->type_ok("comment", "I did some work");
edit_bug_and_return($sel, $bug1_id, $bug_summary);
$sel->is_text_present_ok("I did some work");
$sel->is_text_present_ok("Additional hours worked: 2.6");
# Let's call summarize_time.cgi directly, with no parameters.
$sel->open_ok("/$config->{bugzilla_installation}/summarize_time.cgi");
$sel->title_is("No Bugs Selected");
my $error_msg = trim($sel->get_text("error_msg"));
ok($error_msg =~ /You apparently didn't choose any bugs for viewing/, "No data displayed");
# Search for bugs which have some value in the Hours Worked field.
open_advanced_search_page($sel);
$sel->remove_all_selections("bug_status");
$sel->select_ok("f1", "label=Hours Worked");
$sel->select_ok("o1", "label=is greater than");
$sel->type_ok("v1", "0");
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("found");
# Test dates passed to summarize_time.cgi.
$sel->click_ok("timesummary");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^Time Summary \(\d+ bugs selected\)/);
$sel->check_ok("monthly");
$sel->check_ok("detailed");
$sel->type_ok("start_date", "2009-01-01");
$sel->type_ok("end_date", "2009-04-30");
$sel->click_ok("summarize");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^Time Summary \(\d+ bugs selected\)/);
$sel->is_text_present_ok('regexp:Total of \d+\.\d+ hours worked');
$sel->is_text_present_ok("2009-01-01 to 2009-01-31");
$sel->is_text_present_ok("2009-02-01 to 2009-02-28");
$sel->is_text_present_ok("2009-04-01 to 2009-04-30");
$sel->type_ok("end_date", "2009-04-as");
$sel->click_ok("summarize");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Illegal Date");
$error_msg = trim($sel->get_text("error_msg"));
ok($error_msg =~ /'2009-04-as' is not a legal date/, "Illegal end date");
# Now display one bug only. We cannot do careful checks, because
# the page sums up contributions made by the same user during the same
# month, and so running this script several times per month would
# break checks we may want to do (e.g. by making sure that the contribution
# above has been taken into account). So we are just making sure that
# the page is displayed and throws no error.
go_to_bug($sel, $bug1_id);
$sel->click_ok("//a[contains(text(),'Summarize time')]");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Time Summary for Bug $bug1_id");
$sel->check_ok("inactive");
$sel->check_ok("owner");
$sel->click_ok("summarize");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Time Summary for Bug $bug1_id");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'admin');
set_parameters($sel, { "Administrative Policies" => {"allowuserdeletion-on" => undef} });
# First delete test users, if not deleted correctly during a previous run.
cleanup_users($sel);
# The Master group inherits privs of the Slave group.
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=Master");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Group: Master");
my $group_url = $sel->get_location();
$group_url =~ /group=(\d+)$/;
my $master_gid = $1;
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=Add Group");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Add group");
$sel->type_ok("name", "Slave");
$sel->type_ok("desc", "Members of the Master group are also members of this group");
$sel->uncheck_ok("isactive");
ok(!$sel->is_checked("insertnew"), "Group not added to products by default");
$sel->click_ok("create");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("New Group Created");
my $slave_gid = $sel->get_value("group_id");
$sel->add_selection_ok("members_add", "label=Master");
$sel->click_ok('//input[@value="Update Group"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Group: Slave");
# Create users.
go_to_admin($sel);
$sel->click_ok("link=Users");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Search users');
$sel->click_ok('link=add a new user');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Add user');
$sel->type_ok('login', 'master@selenium.bugzilla.org');
$sel->type_ok('name', 'master-user');
$sel->type_ok('password', 'selenium', 'Enter password');
$sel->type_ok('disabledtext', 'Not for common usage');
$sel->click_ok('add');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Edit user master-user <master@selenium.bugzilla.org>');
$sel->check_ok("//input[\@name='group_$master_gid']");
$sel->click_ok('update');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('User master@selenium.bugzilla.org updated');
$sel->is_text_present_ok('The account has been added to the Master group');
$sel->click_ok("link=add a new user");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Add user');
$sel->type_ok('login', 'slave@selenium.bugzilla.org');
$sel->type_ok('name', 'slave-user');
$sel->type_ok('password', 'selenium', 'Enter password');
$sel->type_ok('disabledtext', 'Not for common usage');
$sel->click_ok('add');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Edit user slave-user <slave@selenium.bugzilla.org>');
$sel->check_ok("//input[\@name='group_$slave_gid']");
$sel->click_ok('update');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('User slave@selenium.bugzilla.org updated');
$sel->is_text_present_ok('The account has been added to the Slave group');
$sel->click_ok("link=add a new user");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Add user');
$sel->type_ok('login', 'reg@selenium.bugzilla.org');
$sel->type_ok('name', 'reg-user');
$sel->type_ok('password', 'selenium', 'Enter password');
$sel->type_ok('disabledtext', 'Not for common usage');
$sel->click_ok('add');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Edit user reg-user <reg@selenium.bugzilla.org>');
# Disabled accounts are not listed by default.
$sel->click_ok('link=find other users');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Search users');
$sel->select_ok('is_enabled', 'label=Enabled');
$sel->click_ok('search');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
ok(!$sel->is_text_present('master@selenium.bugzilla.org'), 'Inactive user account master-user not listed by default');
ok(!$sel->is_text_present('slave@selenium.bugzilla.org'), 'Inactive user account slave-user not listed by default');
ok(!$sel->is_text_present('reg@selenium.bugzilla.org'), 'Inactive user account reg-user not displayed by default');
# Now make sure group inheritance works correctly.
$sel->click_ok('link=find other users');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Search users');
$sel->check_ok('grouprestrict');
$sel->select_ok('groupid', 'label=Master');
$sel->select_ok('matchtype', 'value=substr');
$sel->select_ok('is_enabled', 'label=All');
$sel->click_ok('search');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok('master@selenium.bugzilla.org', 'master-user in Master group');
ok(!$sel->is_text_present('slave@selenium.bugzilla.org'), 'slave-user not in Master group');
ok(!$sel->is_text_present('reg@selenium.bugzilla.org'), 'reg-user not in Master group');
$sel->click_ok('link=find other users');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Search users');
$sel->check_ok('grouprestrict');
$sel->select_ok('groupid', 'label=Slave');
$sel->select_ok('matchtype', 'value=substr');
$sel->select_ok('is_enabled', 'label=All');
$sel->click_ok('search');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok('master@selenium.bugzilla.org', 'master-user in Slave group');
$sel->is_text_present_ok('slave@selenium.bugzilla.org', 'slave-user in Slave group');
ok(!$sel->is_text_present('reg@selenium.bugzilla.org'), 'reg-user not in Slave group');
# Add a regular expression to the Slave group.
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok('link=Slave');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Change Group: Slave');
$sel->type_ok('regexp', '^reg\@.*$');
$sel->click_ok('//input[@value="Update Group"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Group: Slave");
# Test group inheritance again.
go_to_admin($sel);
$sel->click_ok("link=Users");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Search users');
$sel->check_ok('grouprestrict');
$sel->select_ok('groupid', 'label=Master');
$sel->select_ok('matchtype', 'value=substr');
$sel->select_ok('is_enabled', 'label=All');
$sel->click_ok('search');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok('master@selenium.bugzilla.org', 'master-user in Master group');
ok(!$sel->is_text_present('slave@selenium.bugzilla.org'), 'slave-user not in Master group');
ok(!$sel->is_text_present('reg@selenium.bugzilla.org'), 'reg-user not in Master group');
$sel->click_ok('link=find other users');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Search users');
$sel->check_ok('grouprestrict');
$sel->select_ok('groupid', 'label=Slave');
$sel->select_ok('matchtype', 'value=substr');
$sel->select_ok('is_enabled', 'label=All');
$sel->click_ok('search');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->is_text_present_ok('master@selenium.bugzilla.org', 'master-user in Slave group');
$sel->is_text_present_ok('slave@selenium.bugzilla.org', 'slave-user in Slave group');
$sel->is_text_present_ok('reg@selenium.bugzilla.org', 'reg-user in Slave group');
# Remove created users and groups.
cleanup_users($sel);
cleanup_groups($sel, $slave_gid);
logout($sel);
sub cleanup_users {
my $sel = shift;
go_to_admin($sel);
$sel->click_ok("link=Users");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search users");
$sel->type_ok('matchstr', '(master|slave|reg)@selenium.bugzilla.org');
$sel->select_ok('matchtype', 'value=regexp');
$sel->select_ok('is_enabled', 'label=All');
$sel->click_ok("search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select user");
foreach my $user ('master', 'slave', 'reg') {
my $login = $user . '@selenium.bugzilla.org';
next unless $sel->is_text_present($login);
$sel->click_ok("link=$login");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit user ${user}-user <$login>");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Confirm deletion of user $login");
ok(!$sel->is_text_present('You cannot delete this user account'), 'The user can be safely deleted');
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("User $login deleted");
$sel->click_ok('link=show the user list again');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is('Select user');
}
}
sub cleanup_groups {
my ($sel, $slave_gid) = @_;
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("//a[\@href='editgroups.cgi?action=del&group=$slave_gid']");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Delete group 'Slave'");
$sel->is_text_present_ok("Do you really want to delete this group?");
ok(!$sel->is_element_present("removeusers"), 'No direct members in this group');
$sel->click_ok("delete");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Group Deleted");
$sel->is_text_present_ok("The group Slave has been deleted.");
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
log_in($sel, $config, 'tweakparams');
set_parameters($sel, { "User Matching" => {"usemenuforusers-off" => undef,
"maxusermatches" => {type => 'text', value => '0'},
"confirmuniqueusermatch-on" => undef},
"Group Security" => {"usevisibilitygroups-off" => undef}
});
file_bug_in_product($sel, "TestProduct");
$sel->select_ok("component", "TestComponent");
my $bug_summary = "Today is Tuesday";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "Poker Face");
my $bug1_id = create_bug($sel, $bug_summary);
# We enter an incomplete email address. process_bug.cgi must ask
# for confirmation as confirmuniqueusermatch is turned on.
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", $config->{unprivileged_user_login_truncated});
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Confirm Match");
$sel->is_text_present_ok("$config->{unprivileged_user_login_truncated} matched");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id/);
$sel->click_ok("cc_edit_area_showhide");
# We now enter a complete and valid email address, so it must be accepted.
# confirmuniqueusermatch = 1 must not trigger the confirmation page as we
# type the complete email address.
$sel->type_ok("newcc", $config->{unprivileged_user_login});
edit_bug_and_return($sel, $bug1_id, $bug_summary);
# Now test wildcards ("*"). Due to confirmuniqueusermatch being turned on,
# a confirmation page must be displayed.
$sel->click_ok("cc_edit_area_showhide");
$sel->type_ok("newcc", "$config->{unprivileged_user_login_truncated}*");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Confirm Match");
$sel->is_text_present_ok("<$config->{unprivileged_user_login}>");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id/);
$sel->click_ok("cc_edit_area_showhide");
# This will return more than one account.
$sel->type_ok("newcc", "*$config->{common_email}");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Confirm Match");
$sel->is_text_present_ok("*$config->{common_email} matched:");
# Now restrict 'maxusermatches'.
set_parameters($sel, { "User Matching" => {"maxusermatches" => {type => 'text', value => '1'}} });
go_to_bug($sel, $bug1_id);
$sel->click_ok("cc_edit_area_showhide");
# Several user accounts match this partial email address. Due to
# maxusermatches = 1, no email address is suggested.
$sel->type_ok("newcc", "*$config->{common_email}");
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Match Failed");
$sel->is_text_present_ok("matches multiple users");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id/);
$sel->click_ok("cc_edit_area_showhide");
# We now type a complete and valid email address, so no confirmation
# page should be displayed.
$sel->type_ok("newcc", $config->{unprivileged_user_login});
edit_bug($sel, $bug1_id, $bug_summary);
# Now turn on group visibility. It involves important security checks.
set_parameters($sel, { "User Matching" => {"maxusermatches" => {type => 'text', value => '2'}},
"Group Security" => {"usevisibilitygroups-on" => undef}
});
# By default, groups are not visible to themselves, so we have to enable this.
# The tweakparams user has not enough privs to do it himself.
logout($sel);
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Groups");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Edit Groups");
$sel->click_ok("link=tweakparams");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Change Group: tweakparams");
my @groups = $sel->get_select_options("visible_from_add");
if (grep {$_ eq 'tweakparams'} @groups) {
$sel->add_selection_ok("visible_from_add", "label=tweakparams");
$sel->click_ok('//input[@value="Update Group"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Group: tweakparams");
}
logout($sel);
log_in($sel, $config, 'tweakparams');
go_to_bug($sel, $bug1_id);
$sel->click_ok("cc_edit_area_showhide");
# We are not in the same groups as the unprivileged user, so we cannot see him.
$sel->type_ok("newcc", $config->{unprivileged_user_login_truncated});
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Match Failed");
$sel->is_text_present_ok("$config->{unprivileged_user_login_truncated} did not match anything");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id/);
$sel->click_ok("cc_edit_area_showhide");
# This will return too many users (there are at least always three:
# you, the admin and the permanent user (who has admin privs too)).
$sel->type_ok("newcc", $config->{common_email});
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Confirm Match");
$sel->is_text_present_ok("$config->{common_email} matched more than the maximum of 2 users");
$sel->go_back_ok();
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id/);
$sel->click_ok("cc_edit_area_showhide");
# We can always see ourselves.
$sel->type_ok("newcc", $config->{tweakparams_user_login_truncated});
$sel->click_ok("commit");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Confirm Match");
$sel->is_text_present_ok("<$config->{tweakparams_user_login}>");
# Now test user menus. It must NOT display users we are not allowed to see.
set_parameters($sel, { "User Matching" => {"usemenuforusers-on" => undef} });
go_to_bug($sel, $bug1_id);
$sel->click_ok("cc_edit_area_showhide");
my @cc = $sel->get_select_options("newcc");
ok(!grep($_ =~ /$config->{unprivileged_user_login}/, @cc), "$config->{unprivileged_user_login} is not visible");
ok(!grep($_ =~ /$config->{canconfirm_user_login}/, @cc), "$config->{canconfirm_user_login} is not visible");
ok(grep($_ =~ /$config->{admin_user_login}/, @cc), "$config->{admin_user_login} is visible");
ok(grep($_ =~ /$config->{tweakparams_user_login}/, @cc), "$config->{tweakparams_user_login} is visible");
# Reset paramters.
set_parameters($sel, { "User Matching" => {"usemenuforusers-off" => undef,
"maxusermatches" => {type => 'text', value => '0'},
"confirmuniqueusermatch-off" => undef},
"Group Security" => {"usevisibilitygroups-off" => undef}
});
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Update default user preferences.
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Default Preferences");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Default Preferences");
$sel->uncheck_ok("skin-enabled");
$sel->value_is("skin-enabled", "off");
$sel->check_ok("state_addselfcc-enabled");
$sel->select_ok("state_addselfcc", "label=Never");
$sel->check_ok("post_bug_submit_action-enabled");
$sel->select_ok("post_bug_submit_action", "label=Show the updated bug");
$sel->uncheck_ok("zoom_textareas-enabled");
$sel->select_ok("zoom_textareas", "label=Off");
$sel->click_ok("update");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Default Preferences");
# Update own user preferences. Some of them are not editable.
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("General Preferences");
ok(!$sel->is_editable("skin"), "The 'skin' user preference is not editable");
$sel->select_ok("state_addselfcc", "label=Site Default (Never)");
$sel->select_ok("post_bug_submit_action", "label=Site Default (Show the updated bug)");
ok(!$sel->is_editable("zoom_textareas"), "The 'zoom_textareas' user preference is not editable");
$sel->click_ok("update");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("General Preferences");
# File a bug in the 'TestProduct' product. The form fields must follow user prefs.
file_bug_in_product($sel, 'TestProduct');
$sel->value_is("cc", "");
my $bug_summary = "First bug created";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "I'm not in the CC list.");
my $bug1_id = create_bug($sel, $bug_summary);
$sel->value_is("addselfcc", "off");
$sel->type_ok("tag", "sel-tmp");
$sel->select_ok("bug_status", "label=IN_PROGRESS");
edit_bug($sel, $bug1_id, $bug_summary);
$sel->click_ok("summary_edit_action");
$sel->value_is("short_desc", $bug_summary);
$sel->value_is("addselfcc", "off");
# Create a saved search for the 'sel-tmp' tag.
$sel->type_ok("quicksearch_top", "tag:sel-tmp");
$sel->click_ok("find_top");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->type_ok("save_newqueryname", "sel-tmp");
$sel->click_ok("remember");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Search created");
$sel->is_text_present_ok("OK, you have a new search named sel-tmp");
# Leave this page to avoid clicking on the wrong 'sel-tmp' link.
go_to_home($sel, $config);
$sel->click_ok("link=sel-tmp");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: sel-tmp");
$sel->is_text_present_ok("One bug found");
# File another bug in the 'TestProduct' product.
file_bug_in_product($sel, 'TestProduct');
$sel->value_is("cc", "");
my $bug_summary2 = "My second bug";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", "Still not in the CC list");
my $bug2_id = create_bug($sel, $bug_summary2);
$sel->value_is("addselfcc", "off");
$sel->type_ok("tag", "sel-tmp");
edit_bug($sel, $bug2_id, $bug_summary2);
$sel->click_ok("link=sel-tmp");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: sel-tmp");
$sel->is_text_present_ok("2 bugs found");
$sel->click_ok("link=$bug1_id");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
$sel->type_ok("comment", "The next bug I should see is this one.");
edit_bug($sel, $bug1_id, $bug_summary);
$sel->click_ok("summary_edit_action");
$sel->value_is("short_desc", "First bug created");
$sel->is_text_present_ok("The next bug I should see is this one.");
# Remove the saved search. The tag itself still exists.
$sel->click_ok("link=sel-tmp");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: sel-tmp");
$sel->click_ok("forget_search");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Search is gone");
$sel->is_text_present_ok("OK, the sel-tmp search is gone");
# Remove the tag from bugs.
$sel->type_ok("quicksearch_top", "tag:sel-tmp");
$sel->click_ok("find_top");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("2 bugs found");
# We cannot remove tags from several bugs at once (bug 791584).
go_to_bug($sel, $bug1_id);
$sel->type_ok("tag", "");
edit_bug($sel, $bug1_id, $bug_summary);
go_to_bug($sel, $bug2_id);
$sel->type_ok("tag", "");
edit_bug($sel, $bug2_id, $bug_summary2);
$sel->type_ok("quicksearch_top", "tag:sel-tmp");
$sel->click_ok("find_top");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("Zarro Boogs found");
logout($sel);
# Edit own user preferences, now as an unprivileged user.
log_in($sel, $config, 'unprivileged');
$sel->click_ok("link=Preferences");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("General Preferences");
ok(!$sel->is_editable("skin"), "The 'skin' user preference is not editable");
$sel->select_ok("state_addselfcc", "label=Always");
$sel->select_ok("post_bug_submit_action", "label=Show next bug in my list");
ok(!$sel->is_editable("zoom_textareas"), "The 'zoom_textareas' user preference is not editable");
$sel->click_ok("update");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("General Preferences");
# Create a new search named 'my_list'.
open_advanced_search_page($sel);
$sel->remove_all_selections_ok("product");
$sel->add_selection_ok("product", "TestProduct");
$sel->remove_all_selections_ok("bug_status");
$sel->select_ok("bug_id_type", "label=only included in");
$sel->type_ok("bug_id", "$bug1_id , $bug2_id");
$sel->select_ok("order", "label=Bug Number");
$sel->click_ok("Search");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Bug List");
$sel->is_text_present_ok("2 bugs found");
$sel->type_ok("save_newqueryname", "my_list");
$sel->click_ok("remember");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Search created");
$sel->is_text_present_ok("OK, you have a new search named my_list");
# Editing bugs should follow user preferences.
$sel->click_ok("link=my_list");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: my_list");
$sel->click_ok("link=$bug1_id");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
$sel->value_is("addselfcc", "on");
$sel->type_ok("comment", "I should be CC'ed and then I should see the next bug.");
edit_bug($sel, $bug2_id, $bug_summary2);
$sel->is_text_present_ok("The next bug in your list is bug $bug2_id");
ok(!$sel->is_text_present("I should see the next bug"), "The updated bug is no longer displayed");
# The user has no privs, so the short_desc field is not present.
$sel->is_text_present("short_desc", "My second bug");
$sel->value_is("addselfcc", "on");
$sel->click_ok("link=$bug1_id");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_like(qr/^$bug1_id /);
$sel->is_text_present("1 user including you");
# Delete the saved search and log out.
$sel->click_ok("link=my_list");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Bug List: my_list");
$sel->click_ok("forget_search");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Search is gone");
$sel->is_text_present_ok("OK, the my_list search is gone");
logout($sel);
# Restore default user preferences.
log_in($sel, $config, 'admin');
go_to_admin($sel);
$sel->click_ok("link=Default Preferences");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Default Preferences");
$sel->check_ok("skin-enabled");
$sel->uncheck_ok("post_bug_submit_action-enabled");
$sel->select_ok("post_bug_submit_action", "label=Do Nothing");
$sel->click_ok("update");
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is("Default Preferences");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
# Create a new bug. As the reporter, some forms are editable to you.
# But as you don't have editbugs privs, you cannot edit everything.
log_in($sel, $config, 'unprivileged');
file_bug_in_product($sel, 'TestProduct');
ok(!$sel->is_editable("assigned_to"), "The assignee field is not editable");
my $bug_summary = "Greetings from a powerless user";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "File a bug with an empty CC list");
my $bug1_id = create_bug($sel, $bug_summary);
logout($sel);
# Some checks while being logged out.
go_to_bug($sel, $bug1_id);
ok(!$sel->is_element_present("commit"), "Button 'Commit' not available");
my $text = trim($sel->get_text("//fieldset"));
ok($text =~ /You need to log in before you can comment on or make changes to this bug./,
"Addl. comment box not displayed");
# Don't call log_in() here. We explicitly want to use the "log in" link
# in the addl. comment box.
$sel->click_ok("link=log in");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Log in to Bugzilla");
$sel->is_text_present_ok("Bugzilla needs a legitimate login and password to continue");
$sel->type_ok("Bugzilla_login", $config->{unprivileged_user_login}, "Enter login name");
$sel->type_ok("Bugzilla_password", $config->{unprivileged_user_passwd}, "Enter password");
$sel->click_ok("log_in", undef, "Submit credentials");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/^$bug1_id/, "Display bug $bug1_id");
# Neither the (edit) link nor the hidden form must exist, at all.
# But the 'Commit' button does exist.
ok(!$sel->is_element_present("bz_assignee_edit_action"), "No (edit) link displayed for the assignee");
ok(!$sel->is_element_present("assigned_to"), "No hidden assignee field available");
$sel->is_element_present_ok("commit");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More "no_plan";
use QA::Util;
my ($sel, $config) = get_selenium();
unless ($config->{test_extensions}) {
ok(1, "this installation doesn't test extensions. Skipping test_votes.t completely.");
exit;
}
log_in($sel, $config, 'admin');
set_parameters($sel, { "Bug Fields" => {"useclassification-off" => undef},
"Administrative Policies" => {"allowbugdeletion-on" => undef}
});
# Create a new product, so that we can safely play with vote settings.
add_product($sel);
$sel->type_ok("product", "Eureka");
$sel->type_ok("description", "A great new product");
$sel->type_ok("votesperuser", 10);
$sel->type_ok("maxvotesperbug", 5);
$sel->type_ok("votestoconfirm", 3);
$sel->type_ok("component", "Pegasus");
$sel->type_ok("comp_desc", "A constellation in the north hemisphere.");
$sel->type_ok("initialowner", $config->{permanent_user}, "Setting the default owner");
$sel->click_ok('add-product');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Product Created");
# Create a new bug with the CONFIRMED status.
file_bug_in_product($sel, 'Eureka');
# CONFIRMED must be the default bug status for users with editbugs privs.
$sel->selected_label_is("bug_status", "CONFIRMED");
my $bug_summary = "Aries";
$sel->type_ok("short_desc", $bug_summary);
$sel->type_ok("comment", "1st constellation");
my $bug1_id = create_bug($sel, $bug_summary);
# Now vote for this bug.
$sel->click_ok("link=vote");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Votes");
# No comment :-/
my $full_text = trim($sel->get_body_text());
# OK, this is not the most robust regexp, but that's better than nothing.
ok($full_text =~ /only 5 votes allowed per bug in this product/,
"Notice about the number of votes allowed per bug displayed");
$sel->type_ok("bug_$bug1_id", 4);
$sel->click_ok("change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Votes");
$full_text = trim($sel->get_body_text());
# OK, we may get a false positive if another product has the exact same numbers,
# but I have no better idea to check this information.
ok($full_text =~ /4 votes used out of 10 allowed/, "Display the number of votes used");
# File a new bug, now as UNCONFIRMED. We will confirm it by popular votes.
file_bug_in_product($sel, 'Eureka');
$sel->select_ok("bug_status", "UNCONFIRMED");
my $bug_summary2 = "Taurus";
$sel->type_ok("short_desc", $bug_summary2);
$sel->type_ok("comment", "2nd constellation");
my $bug2_id = create_bug($sel, $bug_summary2);
# Put enough votes on this bug to confirm it by popular votes.
$sel->click_ok("link=vote");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Votes");
$sel->type_ok("bug_$bug2_id", 5);
$sel->click_ok("change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Votes");
$sel->is_text_present_ok("Bug $bug2_id confirmed by number of votes");
# File a third bug, again as UNCONFIRMED. We will confirm it
# by decreasing the number required to confirm bugs by popular votes.
file_bug_in_product($sel, 'Eureka');
$sel->select_ok("bug_status", "UNCONFIRMED");
my $bug_summary3 = "Gemini";
$sel->type_ok("short_desc", $bug_summary3);
$sel->type_ok("comment", "3rd constellation");
my $bug3_id = create_bug($sel, $bug_summary3);
# Vote for this bug, but remain below the threshold required
# to confirm the bug by popular votes.
# We also change votes set on other bugs for testing purposes.
$sel->click_ok("link=vote");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Votes");
$sel->type_ok("bug_$bug1_id", 2);
$sel->type_ok("bug_$bug3_id", 2);
$sel->click_ok("change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Votes");
# Illegal change: max is 5 votes per bug!
$sel->type_ok("bug_$bug2_id", 15);
$sel->click_ok("change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Illegal Vote");
my $text = trim($sel->get_text("error_msg"));
ok($text =~ /You may only use at most 5 votes for a single bug in the Eureka product, but you are trying to use 15/,
"Too many votes per bug");
# FIXME - We cannot use go_back_ok() here, because Firefox complains about
# POST data not being stored in its cache. As a workaround, we go to
# the bug we just visited and click the 'vote' link again.
go_to_bug($sel, $bug3_id);
$sel->click_ok("link=vote");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Change Votes");
# Illegal change: max is 10 votes for this product!
$sel->type_ok("bug_$bug2_id", 5);
$sel->type_ok("bug_$bug1_id", 5);
$sel->click_ok("change");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Illegal Vote");
$text = trim($sel->get_text("error_msg"));
ok($text =~ /You tried to use 12 votes in the Eureka product, which exceeds the maximum of 10 votes for this product/,
"Too many votes for this product");
# Decrease the confirmation threshold so that $bug3 becomes confirmed.
edit_product($sel, 'Eureka');
$sel->type_ok("votestoconfirm", 2);
$sel->click_ok("update-product");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Updating Product 'Eureka'");
$full_text = trim($sel->get_body_text());
ok($full_text =~ /Updated number of votes needed to confirm a bug from 3 to 2/,
"Confirming the new number of votes to confirm");
$sel->is_text_present_ok("Bug $bug3_id confirmed by number of votes");
# Decrease the number of votes per bug so that $bug2 is updated.
$sel->click_ok("link='Eureka'");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Edit Product 'Eureka'");
$sel->type_ok("maxvotesperbug", 4);
$sel->click_ok("update-product");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Updating Product 'Eureka'");
$full_text = trim($sel->get_body_text());
ok($full_text =~ /Updated maximum votes per bug from 5 to 4/, "Confirming the new number of votes per bug");
$sel->is_text_present_ok("removed votes for bug $bug2_id from " . $config->{admin_user_login}, undef,
"Removed votes from the admin");
# Go check that $bug2 has been correctly updated.
$sel->click_ok("link=$bug2_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/$bug2_id /);
$text = trim($sel->get_text("votes_container"));
ok($text =~ /4 votes/, "4 votes remaining");
# Decrease the number per user. Bugs should keep at least one vote,
# i.e. not all votes are removed (which was the old behavior).
edit_product($sel, "Eureka");
$sel->type_ok("votesperuser", 5);
$sel->click_ok("update-product");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Updating Product 'Eureka'");
$full_text = trim($sel->get_body_text());
ok($full_text =~ /Updated votes per user from 10 to 5/, "Confirming the new number of votes per user");
$sel->is_text_present_ok("removed votes for bug");
# Go check that $bug3 has been correctly updated.
$sel->click_ok("link=$bug3_id");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_like(qr/$bug3_id /);
$text = trim($sel->get_text("votes_container"));
ok($text =~ /2 votes/, "2 votes remaining");
# Now disable UNCONFIRMED.
edit_product($sel, "Eureka");
$sel->click_ok("allows_unconfirmed");
$sel->click_ok("update-product");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Updating Product 'Eureka'");
$full_text = trim($sel->get_body_text());
ok($full_text =~ /The product no longer allows the UNCONFIRMED status/, "Disable UNCONFIRMED");
# File a new bug. UNCONFIRMED must not be listed as a valid bug status.
file_bug_in_product($sel, "Eureka");
ok(!scalar(grep {$_ eq "UNCONFIRMED"} $sel->get_select_options("bug_status")), "UNCONFIRMED not listed");
my $bug_summary4 = "Cancer";
$sel->type_ok("short_desc", $bug_summary4);
$sel->type_ok("comment", "4th constellation");
my $bug4_id = create_bug($sel, $bug_summary4);
# Now delete the 'Eureka' product.
go_to_admin($sel);
$sel->click_ok("link=Products");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Select product");
$sel->click_ok('//a[@href="editproducts.cgi?action=del&product=Eureka"]');
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Delete Product 'Eureka'");
$full_text = trim($sel->get_body_text());
ok($full_text =~ /There are 4 bugs entered for this product/, "Display warning about existing bugs");
ok($full_text =~ /Pegasus: A constellation in the north hemisphere/, "Display product description");
$sel->click_ok("delete");
$sel->wait_for_page_to_load_ok(WAIT_TIME);
$sel->title_is("Product Deleted");
logout($sel);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use QA::Util;
use MIME::Base64 qw(encode_base64 decode_base64);
use Test::More tests => 187;
my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients();
use constant INVALID_BUG_ID => -1;
use constant INVALID_BUG_ALIAS => random_string(20);
use constant PRIVS_USER => 'QA_Selenium_TEST';
sub attach {
my ($id, $override) = @_;
my %fields = (
ids => [$id],
data => 'data-' . random_string(100),
file_name => 'file_name-' . random_string(60),
summary => 'summary-' . random_string(100),
content_type => 'text/plain',
comment => 'comment-' . random_string(100),
);
foreach my $key (keys %{ $override || {} }) {
my $value = $override->{$key};
if (defined $value) {
$fields{$key} = $value;
}
else {
delete $fields{$key};
}
}
return \%fields;
}
my ($public_bug, $private_bug) =
$xmlrpc->bz_create_test_bugs('private');
my $public_id = $public_bug->{id};
my $private_id = $private_bug->{id};
my @tests = (
# Permissions
{ args => attach($public_id),
error => 'You must log in',
test => 'Logged-out user cannot add an attachment to a public bug',
},
{ args => attach($private_id),
error => "You must log in",
test => 'Logged-out user cannot add an attachment to a private bug',
},
{ user => 'editbugs',
args => attach($private_id),
error => "not authorized to access",
test => "Editbugs user can't add an attachment to a private bug",
},
# Test ID parameter
{ user => 'unprivileged',
args => attach(undef, { ids => undef }),
error => 'a ids argument',
test => 'Failing to pass the "ids" param fails',
},
{ user => 'unprivileged',
args => attach(INVALID_BUG_ID),
error => "not a valid bug number",
test => 'Passing invalid bug id returns error "Invalid Bug ID"',
},
{ user => 'unprivileged',
args => attach(''),
error => "You must enter a valid bug number",
test => 'Passing empty bug id returns error "Invalid Bug ID"',
},
{ user => 'unprivileged',
args => attach(INVALID_BUG_ALIAS),
error => "nor an alias to a bug",
test => 'Passing invalid bug alias returns error "Invalid Bug Alias"',
},
# Test Comment parameter
{ user => 'unprivileged',
args => attach($public_id, { data => undef }),
error => 'a data argument',
test => 'Failing to pass the "data" parameter fails',
},
{ user => 'unprivileged',
args => attach($public_id, { data => '' }),
error => "The file you are trying to attach is empty",
test => 'Passing empty data fails',
},
{ user => 'unprivileged',
args => attach($public_id, { data => random_string(300_000) }),
error => "Attachments cannot be more than",
test => "Passing an attachment that's too large fails",
},
# Test the private parameter
{ user => 'unprivileged',
args => attach($public_id, { is_private => 1 }),
error => 'attachments as private',
test => 'Unprivileged user cannot add a private attachment'
},
# Content-type
{ user => 'unprivileged',
args => attach($public_id, { content_type => 'foo/bar' }),
error => "Valid types must be of the form",
test => "Well-formed but invalid content type fails",
},
{ user => 'unprivileged',
args => attach($public_id, { content_type => undef }),
error => 'Valid types must be of the form',
test => "Failing to pass content_type fails",
},
{ user => 'unprivileged',
args => attach($public_id, { content_type => '' }),
error => 'Valid types must be of the form',
test => "Empty content type fails",
},
# Summary
{ user => 'unprivileged',
args => attach($public_id, { summary => undef }),
error => 'You must enter a description for the attachment',
test => "Failing to pass summary fails",
},
{ user => 'unprivileged',
args => attach($public_id, { summary => '' }),
error => 'You must enter a description for the attachment',
test => "Empty summary fails",
},
# Filename
{ user => 'unprivileged',
args => attach($public_id, { file_name => undef }),
error => 'You did not specify a file to attach',
test => "Failing to pass file_name fails",
},
{ user => 'unprivileged',
args => attach($public_id, { file_name => '' }),
error => 'You did not specify a file to attach',
test => "Empty file_name fails",
},
# Success tests
{ user => 'unprivileged',
args => attach($public_id),
test => 'Unprivileged user can add an attachment to a public bug',
},
{ user => 'unprivileged',
args => attach($public_id, { is_patch => 1, content_type => undef }),
test => 'Attaching a patch with no content type works',
},
{ user => 'unprivileged',
args => attach($public_id, { is_patch => 1,
content_type => 'application/octet-stream' }),
test => 'Attaching a patch with a bad content_type works',
},
{ user => PRIVS_USER,
args => attach($private_id),
test => 'Privileged user can add an attachment to a private bug',
},
{ user => PRIVS_USER,
args => attach($public_id, { is_private => 1 }),
test => 'Insidergroup user can add a private attachment',
},
);
$jsonrpc_get->bz_call_fail('Bug.add_attachment', attach($public_id),
'must use HTTP POST', 'add_attachment fails over GET');
foreach my $rpc ($jsonrpc, $xmlrpc) {
$rpc->bz_run_tests(tests => \@tests, method => 'Bug.add_attachment',
post_success => \&post_success, pre_call => \&pre_call);
}
# We have to encode data manually when using JSON-RPC, else it fails.
sub pre_call {
my ($t, $rpc) = @_;
return if !$rpc->isa('QA::RPC::JSONRPC');
return if !defined $t->{args}->{data};
$t->{args}->{data} = encode_base64($t->{args}->{data}, '');
}
sub post_success {
my ($call, $t, $rpc) = @_;
my $ids = $call->result->{ids};
$call = $rpc->bz_call_success("Bug.attachments", {attachment_ids => $ids});
my $attachments = $call->result->{attachments};
foreach my $id (keys %$attachments) {
my $attachment = $attachments->{$id};
if ($t->{args}->{is_private}) {
ok($attachment->{is_private},
$rpc->TYPE . ": Attachment $id is private");
}
else {
ok(!$attachment->{is_private},
$rpc->TYPE . ": Attachment $id is NOT private");
}
if ($t->{args}->{is_patch}) {
is($attachment->{content_type}, 'text/plain',
$rpc->TYPE . ": Patch $id content type is text/plain");
}
else {
is($attachment->{content_type}, $t->{args}->{content_type},
$rpc->TYPE . ": Attachment $id content type is correct");
}
if ($rpc->isa('QA::RPC::JSONRPC')) {
# We encoded data in pre_call(), so we have to restore it to its original content.
$t->{args}->{data} = decode_base64($t->{args}->{data});
$attachment->{data} = decode_base64($attachment->{data});
}
is($attachment->{data}, $t->{args}->{data},
$rpc->TYPE . ": Attachment $id data is correct");
}
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#############################################
# Test for xmlrpc call to Bug.add_comment() #
#############################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use QA::Util;
use Test::More tests => 141;
my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients();
use constant INVALID_BUG_ID => -1;
use constant INVALID_BUG_ALIAS => 'aaaaaaa12345';
use constant PRIVS_USER => 'QA_Selenium_TEST';
use constant TIMETRACKING_USER => 'admin';
use constant TEST_COMMENT => '--- Test Comment From QA Tests ---';
use constant TOO_LONG_COMMENT => 'a' x 100000;
my @tests = (
# Permissions
{ args => { id => 'public_bug', comment => TEST_COMMENT },
error => 'You must log in',
test => 'Logged-out user cannot comment on a public bug',
},
{ args => { id => 'private_bug', comment => TEST_COMMENT },
error => "You must log in",
test => 'Logged-out user cannot comment on a private bug',
},
{ user => 'unprivileged',
args => { id => 'private_bug', comment => TEST_COMMENT },
error => "not authorized to access",
test => "Unprivileged user can't comment on a private bug",
},
# Test ID parameter
{ user => 'unprivileged',
args => { comment => TEST_COMMENT },
error => 'a id argument',
test => 'Failing to pass the "id" param fails',
},
{ user => 'unprivileged',
args => { id => INVALID_BUG_ID, comment => TEST_COMMENT },
error => "not a valid bug number",
test => 'Passing invalid bug id returns error "Invalid Bug ID"',
},
{ user => 'unprivileged',
args => { id => '', comment => TEST_COMMENT },
error => "You must enter a valid bug number",
test => 'Passing empty bug id param returns error "Invalid Bug ID"',
},
{ user => 'unprivileged',
args => { id => INVALID_BUG_ALIAS, comment => TEST_COMMENT },
error => "nor an alias to a bug",
test => 'Passing invalid bug alias returns error "Invalid Bug Alias"',
},
# Test Comment parameter
{ user => 'unprivileged',
args => { id => 'public_bug' },
error => 'a comment argument',
test => 'Failing to pass the "comment" parameter fails',
},
{ user => 'unprivileged',
args => { id => 'public_bug', comment => '' },
error => "a comment argument",
test => 'Passing an empty comment fails',
},
{ user => 'unprivileged',
args => { id => 'public_bug', comment => ' ' },
error => 'a comment argument',
test => 'Passing only a space for comment fails',
},
{ user => 'unprivileged',
args => { id => 'public_bug', comment => " \t\n\n\r\n\r\n\r " },
error => 'a comment argument',
test => 'Passing only whitespace (including newlines) fails',
},
{ user => 'unprivileged',
args => { id => 'public_bug', comment => TOO_LONG_COMMENT },
error => "cannot be longer than",
test => "Passing a comment that's too long fails",
},
# Testing the "private" parameter happens in the tests for Bug.comments
# Test work_time parameter
# FIXME Should be testing permissions on the work_time parameter,
# but we currently have no way to verify whether or not time was
# added to the bug, and there's no error thrown if you lack perms.
{ user => 'admin',
args => { id => 'public_bug', comment => TEST_COMMENT,
work_time => 'aaa' },
error => "is not a numeric value",
test => "Passing a non-numeric work_time fails",
},
{ user => 'admin',
args => { id => 'public_bug', comment => TEST_COMMENT,
work_time => '1234567890' },
error => 'more than the maximum',
test => 'Passing too large of a work_time fails',
},
{ user => 'admin',
args => { id => 'public_bug', comment => '',
work_time => '1.0' },
error => 'a comment argument',
test => 'Passing a work_time with an empty comment fails',
},
# Success tests
{ user => 'unprivileged',
args => { id => 'public_bug', comment => TEST_COMMENT },
test => 'Unprivileged user can add a comment to a public bug',
},
{ user => 'unprivileged',
args => { id => 'public_bug', comment => " \n" . TEST_COMMENT },
test => 'Can add a comment to a bug where the first line is whitespace',
},
{ user => 'QA_Selenium_TEST',
args => { id => 'private_bug', comment => TEST_COMMENT },
test => 'Privileged user can add a comment to a private bug',
check_privacy => 1,
},
{ user => 'QA_Selenium_TEST',
args => { id => 'public_bug', comment => TEST_COMMENT,
is_private => 1 },
test => 'Insidergroup user can add a private comment',
check_privacy => 1,
},
{ user => 'admin',
args => { id => 'public_bug', comment => TEST_COMMENT,
work_time => '1.5' },
test => 'Timetracking user can add work_time to a bug',
},
# FIXME Need to verify that the comment added actually has work_time.
);
$jsonrpc_get->bz_call_fail('Bug.add_comment',
{ id => 'public_bug', comment => TEST_COMMENT },
'must use HTTP POST', 'add_comment fails over GET');
foreach my $rpc ($jsonrpc, $xmlrpc) {
$rpc->bz_run_tests(tests => \@tests, method => 'Bug.add_comment',
post_success => \&post_success);
}
sub post_success {
my ($call, $t, $rpc) = @_;
return unless $t->{check_privacy};
my $comment_id = $call->result->{id};
my $result = $rpc->bz_call_success('Bug.comments', {comment_ids => [$comment_id]});
if ($t->{args}->{is_private}) {
ok($result->result->{comments}->{$comment_id}->{is_private},
$rpc->TYPE . ": Comment $comment_id is private");
}
else {
ok(!$result->result->{comments}->{$comment_id}->{is_private},
$rpc->TYPE . ": Comment $comment_id is NOT private");
}
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use QA::Util;
use QA::Tests qw(STANDARD_BUG_TESTS PRIVATE_BUG_USER);
use Data::Dumper;
use List::Util qw(first);
use MIME::Base64;
use Test::More tests => 313;
my ($config, @clients) = get_rpc_clients();
################
# Bug ID Tests #
################
our %attachments;
sub post_bug_success {
my ($call, $t) = @_;
my $bugs = $call->result->{bugs};
is(scalar keys %$bugs, 1, "Got exactly one bug")
or diag(Dumper($call->result));
my $bug_attachments = (values %$bugs)[0];
# Collect attachment ids
foreach my $alias (qw(public_bug private_bug)) {
foreach my $is_private (0, 1) {
my $find_desc = "${alias}_${is_private}";
my $attachment = first { $_->{summary} eq $find_desc }
reverse @$bug_attachments;
if ($attachment) {
$attachments{$find_desc} = $attachment->{id};
}
}
}
}
foreach my $rpc (@clients) {
$rpc->bz_run_tests(tests => STANDARD_BUG_TESTS, method => 'Bug.attachments',
post_success => \&post_bug_success);
}
foreach my $alias (qw(public_bug private_bug)) {
foreach my $is_private (0, 1) {
ok($attachments{"${alias}_${is_private}"},
"Found attachment id for ${alias}_${is_private}");
}
}
####################
# Attachment Tests #
####################
my $content_file = $config->{bugzilla_path} . '/xt/config/generate_test_data.pl';
open(my $fh, '<', $content_file) or die "$content_file: $!";
my $content;
{ local $/; $content = <$fh>; }
close($fh);
# Access tests for public/private stuff, and also validate that the
# format of each return value is correct.
my @tests = (
# Logged-out user
{ args => { attachment_ids => [$attachments{'public_bug_0'}] },
test => 'Logged-out user can access public attachment on public'
. ' bug by id',
},
{ args => { attachment_ids => [$attachments{'public_bug_1'}] },
test => 'Logged-out user cannot access private attachment on public bug',
error => 'Sorry, you are not authorized',
},
{ args => { attachment_ids => [$attachments{'private_bug_0'}] },
test => 'Logged-out user cannot access attachments by id on private bug',
error => 'You are not authorized to access',
},
{ args => { attachment_ids => [$attachments{'private_bug_1'}] },
test => 'Logged-out user cannot access private attachment on '
. ' private bug',
error => 'You are not authorized to access',
},
# Logged-in, unprivileged user.
{ user => 'unprivileged',
args => { attachment_ids => [$attachments{'public_bug_0'}] },
test => 'Logged-in user can see a public attachment on a public bug by id',
},
{ user => 'unprivileged',
args => { attachment_ids => [$attachments{'public_bug_1'}] },
test => 'Logged-in user cannot access private attachment on public bug',
error => 'Sorry, you are not authorized',
},
{ user => 'unprivileged',
args => { attachment_ids => [$attachments{'private_bug_0'}] },
test => 'Logged-in user cannot access attachments by id on private bug',
error => "You are not authorized to access",
},
{ user => 'unprivileged',
args => { attachment_ids => [$attachments{'private_bug_1'}] },
test => 'Logged-in user cannot access private attachment on private bug',
error => "You are not authorized to access",
},
# User who can see private bugs and private attachments
{ user => PRIVATE_BUG_USER,
args => { attachment_ids => [$attachments{'public_bug_1'}] },
test => PRIVATE_BUG_USER . ' can see private attachment on public bug',
},
{ user => PRIVATE_BUG_USER,
args => { attachment_ids => [$attachments{'private_bug_1'}] },
test => PRIVATE_BUG_USER . ' can see private attachment on private bug',
},
);
sub post_success {
my ($call, $t, $rpc) = @_;
is(scalar keys %{ $call->result->{attachments} }, 1,
"Got exactly one attachment");
my $attachment = (values %{ $call->result->{attachments} })[0];
cmp_ok($attachment->{last_change_time}, '=~', $rpc->DATETIME_REGEX,
"last_change_time is in the right format");
cmp_ok($attachment->{creation_time}, '=~', $rpc->DATETIME_REGEX,
"creation_time is in the right format");
is($attachment->{is_obsolete}, 0, 'is_obsolete is 0');
cmp_ok($attachment->{bug_id}, '=~', qr/^\d+$/, "bug_id is an integer");
cmp_ok($attachment->{id}, '=~', qr/^\d+$/, "id is an integer");
is($attachment->{content_type}, 'application/x-perl',
"content_type is correct");
cmp_ok($attachment->{file_name}, '=~', qr/^\w+\.pl$/,
"filename is in the expected format");
is($attachment->{creator}, $config->{QA_Selenium_TEST_user_login},
"creator is the correct user");
my $data = $attachment->{data};
$data = decode_base64($data) if $rpc->isa('QA::RPC::JSONRPC');
is($data, $content, 'data is correct');
is($attachment->{size}, length($data), "size matches data's size");
}
foreach my $rpc (@clients) {
$rpc->bz_run_tests(method => 'Bug.attachments', tests => \@tests,
post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
##########################################
# Test for xmlrpc call to Bug.comments() #
##########################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use DateTime;
use QA::Util;
use QA::Tests qw(STANDARD_BUG_TESTS PRIVATE_BUG_USER);
use Test::More tests => 331;
my ($config, @clients) = get_rpc_clients();
# These gets populated when we call Bug.add_comment.
our $creation_time;
our %comments = (
public_comment_public_bug => 0,
public_comment_private_bug => 0,
private_comment_public_bug => 0,
private_comment_private_bug => 0,
);
sub test_comments {
my ($comments_returned, $call, $t, $rpc) = @_;
my $comment = $comments_returned->[0];
ok($comment->{bug_id}, "bug_id exists");
# FIXME At some point we should test attachment_id here.
if ($t->{args}->{comment_ids}) {
my $expected_id = $t->{args}->{comment_ids}->[0];
is($comment->{id}, $expected_id, "comment id is correct");
my %reverse_map = reverse %comments;
my $expected_text = $reverse_map{$expected_id};
is($comment->{text}, $expected_text, "comment has the correct text");
my $priv_login = $rpc->bz_config->{PRIVATE_BUG_USER . '_user_login'};
is($comment->{creator}, $priv_login, "comment creator is correct");
my $creation_day;
if ($rpc->isa('QA::RPC::XMLRPC')) {
$creation_day = $creation_time->ymd('');
}
else {
$creation_day = $creation_time->ymd;
}
like($comment->{time}, qr/^\Q${creation_day}\ET\d\d:\d\d:\d\d/,
"comment time has the right format");
}
else {
foreach my $field (qw(id text creator time)) {
ok(defined $comment->{$field}, "$field is defined");
}
}
}
################
# Bug ID Tests #
################
sub post_bug_success {
my ($call, $t) = @_;
my @bugs = values %{ $call->result->{bugs} };
is(scalar @bugs, 1, "Got exactly one bug");
my @comments = map { @{ $_->{comments} } } @bugs;
test_comments(\@comments, @_);
}
foreach my $rpc (@clients) {
$rpc->bz_run_tests(tests => STANDARD_BUG_TESTS, method => 'Bug.comments',
post_success => \&post_bug_success);
}
####################
# Comment ID Tests #
####################
# First, create comments using add_comment.
my @add_comment_tests;
foreach my $key (keys %comments) {
$key =~ /^([a-z]+)_comment_(\w+)$/;
my $is_private = ($1 eq 'private' ? 1 : 0);
my $bug_alias = $2;
push(@add_comment_tests, { args => { id => $bug_alias, comment => $key,
private => $is_private },
test => "Add comment: $key",
user => PRIVATE_BUG_USER });
}
# Set the comment id for each comment that we add, so we can test getting
# them back, later.
sub post_add {
my ($call, $t) = @_;
my $key = $t->{args}->{comment};
$comments{$key} = $call->result->{id};
}
$creation_time = DateTime->now();
# We only need to create these comments once, with one of the interfaces.
$clients[0]->bz_run_tests(
tests => \@add_comment_tests, method => 'Bug.add_comment',
post_success => \&post_add);
# Now check access on each private and public comment
my @comment_tests = (
# Logged-out user
{ args => { comment_ids => [$comments{'public_comment_public_bug'}] },
test => 'Logged-out user can access public comment on public bug by id',
},
{ args => { comment_ids => [$comments{'private_comment_public_bug'}] },
test => 'Logged-out user cannot access private comment on public bug',
error => 'is private',
},
{ args => { comment_ids => [$comments{'public_comment_private_bug'}] },
test => 'Logged-out user cannot access comments by id on private bug',
error => 'You are not authorized to access',
},
{ args => { comment_ids => [$comments{'private_comment_private_bug'}] },
test => 'Logged-out user cannot access private comment on private bug',
error => 'You are not authorized to access',
},
# Logged-in, unprivileged user.
{ user => 'unprivileged',
args => { comment_ids => [$comments{'public_comment_public_bug'}] },
test => 'Logged-in user can see a public comment on a public bug by id',
},
{ user => 'unprivileged',
args => { comment_ids => [$comments{'private_comment_public_bug'}] },
test => 'Logged-in user cannot access private comment on public bug',
error => 'is private',
},
{ user => 'unprivileged',
args => { comment_ids => [$comments{'public_comment_private_bug'}] },
test => 'Logged-in user cannot access comments by id on private bug',
error => "You are not authorized to access",
},
{ user => 'unprivileged',
args => { comment_ids => [$comments{'private_comment_private_bug'}] },
test => 'Logged-in user cannot access private comment on private bug',
error => "You are not authorized to access",
},
# User who can see private bugs and private comments
{ user => PRIVATE_BUG_USER,
args => { comment_ids => [$comments{'private_comment_public_bug'}] },
test => PRIVATE_BUG_USER . ' can see private comment on public bug',
},
{ user => PRIVATE_BUG_USER,
args => { comment_ids => [$comments{'private_comment_private_bug'}] },
test => PRIVATE_BUG_USER . ' can see private comment on private bug',
},
);
sub post_comments {
my ($call) = @_;
my @comments = values %{ $call->result->{comments} };
is(scalar @comments, 1, "Got exactly one comment");
test_comments(\@comments, @_);
}
foreach my $rpc (@clients) {
$rpc->bz_run_tests(tests => \@comment_tests, method => 'Bug.comments',
post_success => \&post_comments);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
########################################
# Test for xmlrpc call to Bug.create() #
########################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Storable qw(dclone);
use Test::More tests => 293;
use QA::Util;
use QA::Tests qw(create_bug_fields PRIVATE_BUG_USER);
my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients();
########################
# Bug.create() testing #
########################
my $bug_fields = create_bug_fields($config);
# hash to contain all the possible $bug_fields values that
# can be passed to createBug()
my $fields = {
summary => {
undefined => {
faultstring => 'You must enter a summary for this bug',
value => undef
},
},
product => {
undefined => { faultstring => 'You must select/enter a product.', value => undef },
invalid =>
{ faultstring => 'does not exist', value => 'does-not-exist' },
},
component => {
undefined => {
faultstring => 'you must first choose a component',
value => undef
},
invalid => {
faultstring => "There is no component named 'does-not-exist'",
value => 'does-not-exist'
},
},
version => {
undefined =>
{ faultstring => 'You must select/enter a version.', value => undef },
invalid => {
faultstring => "There is no version named 'does-not-exist' in the",
value => 'does-not-exist'
},
},
platform => {
undefined =>
{ faultstring => 'You must select/enter a Hardware.',
value => '' },
invalid => {
faultstring => "There is no Hardware named 'does-not-exist'.",
value => 'does-not-exist'
},
},
status => {
invalid => {
faultstring => "There is no status named 'does-not-exist'",
value => 'does-not-exist'
},
},
severity => {
undefined =>
{ faultstring => 'You must select/enter a Severity.',
value => '' },
invalid => {
faultstring => "There is no Severity named 'does-not-exist'.",
value => 'does-not-exist'
},
},
priority => {
undefined =>
{ faultstring => 'You must select/enter a Priority.',
value => '' },
invalid => {
faultstring => "There is no Priority named 'does-not-exist'.",
value => 'does-not-exist'
},
},
op_sys => {
undefined => {
faultstring => 'You must select/enter a OS.',
value => ''
},
invalid => {
faultstring => "There is no OS named 'does-not-exist'.",
value => 'does-not-exist'
},
},
cc => {
invalid => {
faultstring => 'not a valid username',
value => ['nonuserATbugillaDOTorg']
},
},
assigned_to => {
invalid => {
faultstring => "There is no user named 'does-not-exist'",
value => 'does-not-exist'
},
},
qa_contact => {
invalid => {
faultstring => "There is no user named 'does-not-exist'",
value => 'does-not-exist'
},
},
alias => {
long => {
faultstring => 'Bug aliases cannot be longer than 40 characters',
value => 'MyyyyyyyyyyyyyyyyyyBugggggggggggggggggggggg'
},
existing => {
faultstring => 'already taken the alias',
value => 'public_bug'
},
numeric => {
faultstring => 'aliases cannot be merely numbers',
value => '12345'
},
commma_or_space_separated => {
faultstring => 'contains one or more commas or spaces',
value => ['Bug 12345']
},
},
groups => {
non_existent => {
faultstring => 'either this group does not exist, or you are not allowed to restrict bugs to this group',
value => [random_string(20)],
},
},
comment_is_private => {
invalid => {
faultstring => 'you are not allowed to.+comments.+private',
value => 1,
}
},
};
$jsonrpc_get->bz_call_fail('Bug.create', $bug_fields,
'must use HTTP POST', 'create fails over GET');
my @tests = (
{ args => $bug_fields,
error => "You must log in",
test => "Cannot file bugs as a logged-out user",
},
{ user => PRIVATE_BUG_USER,
args => { %$bug_fields, product => 'QA-Selenium-TEST',
component => 'QA-Selenium-TEST',
target_milestone => 'QAMilestone',
version => 'QAVersion',
groups => ['QA-Selenium-TEST'],
# These are set here because we can't actually set them,
# and we need the values to be correct for post_success.
qa_contact => $config->{PRIVATE_BUG_USER . '_user_login'},
status => 'UNCONFIRMED' },
test => "Authorized user can file a bug against a group",
},
{ user => PRIVATE_BUG_USER,
args => { %$bug_fields, comment_is_private => 1,
# These are here because PRIVATE_BUG_USER can't set them
# and we need their values to be correct for post_success.
assigned_to => $config->{'permanent_user'},
qa_contact => '',
status => 'UNCONFIRMED' },
test => "Insider can create a private description"
},
{ user => 'editbugs',
args => $bug_fields,
test => "Creating a bug with standard values succeeds",
},
);
# Convert the $fields tests into standard bz_run_tests format.
foreach my $field (sort keys %$fields) {
my $test_values = $fields->{$field};
foreach my $test_name (sort keys %$test_values) {
my $input_fields = dclone($bug_fields);
my $check_value = $test_values->{$test_name}->{value};
my $error = $test_values->{$test_name}->{faultstring};
$input_fields->{$field} = $check_value;
my $test = { user => 'editbugs', args => $input_fields,
error => $error,
test => "$field $test_name: fails as expected" };
push(@tests, $test);
}
}
sub post_success {
my ($call, $t, $rpc) = @_;
my $id = $call->result->{id};
ok($id, $rpc->TYPE . ": Result has an id: $id");
my $get_call = $rpc->bz_call_success('Bug.get', { ids => [$id] });
my $bug = $get_call->result->{bugs}->[0];
my $expect = dclone $t->{args};
my $comment_is_private = delete $expect->{comment_is_private};
$expect->{creator} = $rpc->bz_config->{$t->{user} . '_user_login'};
my @fields = keys %$expect;
$rpc->bz_test_bug(\@fields, $bug, $expect, $t);
my $comment_call = $rpc->bz_call_success('Bug.comments', { ids => [$id] });
my $comment = $comment_call->result->{bugs}->{$id}->{comments}->[0];
is($comment->{is_private} ? 1 : 0, $comment_is_private ? 1 : 0,
$rpc->TYPE . ": comment privacy is correct");
}
foreach my $rpc ($jsonrpc, $xmlrpc) {
$rpc->bz_run_tests(tests => \@tests, method => 'Bug.create',
post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Data::Dumper;
use Test::More;
use List::Util qw(first);
use QA::Util;
my ($config, @clients) = get_rpc_clients();
plan tests => ($config->{test_extensions} ? 1338 : 1320);
use constant INVALID_FIELD_NAME => 'invalid_field';
use constant INVALID_FIELD_ID => -1;
sub GLOBAL_GENERAL_FIELDS {
my @fields = qw(
attach_data.thedata
attachments.description
attachments.filename
attachments.isobsolete
attachments.ispatch
attachments.isprivate
attachments.mimetype
attachments.submitter
flagtypes.name
requestees.login_name
setters.login_name
alias
assigned_to
blocked
bug_file_loc
bug_group
bug_id
cc
cclist_accessible
classification
commenter
content
creation_ts
days_elapsed
delta_ts
dependson
everconfirmed
keywords
longdesc
longdescs.isprivate
owner_idle_time
product
qa_contact
reporter
reporter_accessible
see_also
short_desc
status_whiteboard
deadline
estimated_time
percentage_complete
remaining_time
work_time
);
push(@fields, 'votes') if QA::Util::get_config()->{test_extensions};
return @fields;
}
use constant STANDARD_SELECT_FIELDS =>
qw(bug_severity bug_status op_sys priority rep_platform resolution);
use constant ALL_SELECT_FIELDS => (STANDARD_SELECT_FIELDS,
qw(cf_qa_status cf_single_select));
use constant PRODUCT_FIELDS => qw(version target_milestone component);
use constant ALL_FIELDS => (GLOBAL_GENERAL_FIELDS, ALL_SELECT_FIELDS,
PRODUCT_FIELDS);
use constant MANDATORY_FIELDS => qw(short_desc product version component);
use constant PUBLIC_PRODUCT => 'Another Product';
use constant PRIVATE_PRODUCT => 'QA-Selenium-TEST';
sub get_field {
my ($fields, $field) = @_;
return first { $_->{name} eq $field } @$fields;
}
sub get_products_from_field {
my $field = shift;
my %products;
foreach my $value (@{ $field->{values} }) {
foreach my $vis_value (@{ $value->{visibility_values} }) {
$products{$vis_value} = 1;
}
}
return \%products;
}
our %field_ids;
foreach my $rpc (@clients) {
my $call = $rpc->bz_call_success('Bug.fields');
my $fields = $call->result->{fields};
foreach my $field (ALL_FIELDS) {
my $field_data = get_field($fields, $field);
ok($field_data, "$field is in the returned result")
or diag(Dumper($fields));
$field_ids{$field} = $field_data->{id};
if (grep($_ eq $field, MANDATORY_FIELDS)) {
ok($field_data->{is_mandatory}, "$field is mandatory");
}
else {
ok(!$field_data->{is_mandatory}, "$field is not mandatory");
}
}
foreach my $field (ALL_SELECT_FIELDS, PRODUCT_FIELDS) {
my $field_data = get_field($fields, $field);
ok(defined $field_data->{visibility_values},
"$field has visibility_values defined");
my $field_vis_undefs = grep { !defined $_ }
@{ $field_data->{visibility_values} };
is($field_vis_undefs, 0, "$field.visibility_values has no undefs")
or diag(Dumper($field_data->{visibility_values}));
ok(defined $field_data->{values},
"$field has 'values' defined");
my $num_values = scalar @{ $field_data->{values} };
ok($num_values, "$field has $num_values values");
# The first bug status is a fake one and has no name, so we choose the 2nd item.
my $first_value = $field_data->{values}->[1];
ok(defined $first_value->{name}, 'The first value has a name')
or diag(Dumper($field_data->{values}));
# The sortkey for milestones can be negative.
cmp_ok($first_value->{sortkey}, '=~', qr/^-?\d+$/,
"The first value has a numeric sortkey");
ok(defined $first_value->{visibility_values},
"$field has visibilty_values defined on its first value")
or diag(Dumper($field_data->{values}));
my @value_visibility_values = map { @{ $_->{visibility_values} } }
@{ $field_data->{values} };
my $undefs = grep { !defined $_ } @value_visibility_values;
is($undefs, 0,
"$field.values.visibility_values has no undefs");
}
foreach my $field (PRODUCT_FIELDS) {
my $field_data = get_field($fields, $field);
is($field_data->{value_field}, 'product',
"The value_field for $field is 'product'");
my $products = get_products_from_field($field_data);
ok($products->{+PUBLIC_PRODUCT},
"$field values are returned for the public product");
ok(!$products->{+PRIVATE_PRODUCT},
"No $field values are returned for the private product");
}
}
my @all_tests = (
{ args => { ids => [values %field_ids],
names => [ALL_FIELDS] },
test => 'Getting all fields by name and id simultaneously',
count => scalar ALL_FIELDS
},
{ args => { names => [INVALID_FIELD_NAME] },
error => "There is no field named",
test => 'Invalid field name'
},
{ args => { ids => [INVALID_FIELD_ID] },
error => 'must be numeric',
test => 'Invalid field id'
},
{ user => 'QA_Selenium_TEST',
args => { names => [PRODUCT_FIELDS] },
test => 'Getting product-specific fields as a privileged user',
count => scalar PRODUCT_FIELDS,
product_private_values => 1
},
);
foreach my $field (ALL_FIELDS) {
push(@all_tests,
{ args => { names => [$field] },
test => "Logged-out users can get the $field field by name" });
push(@all_tests,
{ args => { ids => [$field_ids{$field}] },
test => "Logged-out users can get the $field by id" });
}
sub post_success {
my ($call, $t) = @_;
my $fields = $call->result->{fields};
my $count = $t->{count};
$count = 1 if !defined $count;
is(scalar @$fields, $count, "Exactly $count field(s) returned");
if ($t->{product_private_values}) {
foreach my $field (@$fields) {
my $name = $field->{name};
my $field_data = get_field($fields, $name);
my $products = get_products_from_field($field_data);
ok($products->{+PUBLIC_PRODUCT},
"$name values are returned for the public product");
ok($products->{+PRIVATE_PRODUCT},
"$name values are returned for the private product");
}
}
}
foreach my $rpc (@clients) {
$rpc->bz_run_tests(tests => \@all_tests, method => 'Bug.fields',
post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
###########################################
# Test for xmlrpc call to Bug.get() #
###########################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Data::Dumper;
use DateTime;
use QA::Util;
use QA::Tests qw(bug_tests PRIVATE_BUG_USER);
use Test::More tests => 988;
my ($config, @clients) = get_rpc_clients();
my $xmlrpc = $clients[0];
our $creation_time = DateTime->now();
our ($public_bug, $private_bug) = $xmlrpc->bz_create_test_bugs('private');
my $private_id = $private_bug->{id};
my $public_id = $public_bug->{id};
my $base_url = $config->{browser_url} . "/"
. $config->{bugzilla_installation} . '/';
# Set a few fields on the private bug, including setting up
# a dependency relationship.
$xmlrpc->bz_log_in(PRIVATE_BUG_USER);
$xmlrpc->bz_call_success('Bug.update', {
ids => [$private_id],
blocks => { set => [$public_id] },
dupe_of => $public_id,
is_creator_accessible => 0,
keywords => { set => ['test-keyword-1', 'test-keyword-2'] },
see_also => { add => ["${base_url}show_bug.cgi?id=$public_id",
"http://landfill.bugzilla.org/show_bug.cgi?id=123456"] },
cf_qa_status => ['in progress', 'verified'],
cf_single_select => 'two',
}, 'Update the private bug');
$xmlrpc->bz_call_success('User.logout');
$private_bug->{blocks} = [$public_id];
$private_bug->{dupe_of} = $public_id;
$private_bug->{status} = 'RESOLVED';
$private_bug->{is_open} = 0;
$private_bug->{resolution} = 'DUPLICATE';
$private_bug->{is_creator_accessible} = 0;
$private_bug->{is_cc_accessible} = 1;
$private_bug->{keywords} = ['test-keyword-1', 'test-keyword-2'];
$private_bug->{see_also} = ["${base_url}show_bug.cgi?id=$public_id",
"http://landfill.bugzilla.org/show_bug.cgi?id=123456"];
$private_bug->{cf_qa_status} = ['in progress', 'verified'];
$private_bug->{cf_single_select} = 'two';
$public_bug->{depends_on} = [$private_id];
$public_bug->{dupe_of} = undef;
$public_bug->{resolution} = '';
$public_bug->{is_open} = 1;
$public_bug->{is_creator_accessible} = 1;
$public_bug->{is_cc_accessible} = 1;
$public_bug->{keywords} = [];
# Local Bugzilla bugs are automatically updated.
$public_bug->{see_also} = ["${base_url}show_bug.cgi?id=$private_id"];
$public_bug->{cf_qa_status} = [];
$public_bug->{cf_single_select} = '---';
# Fill in the timetracking fields on the public bug.
$xmlrpc->bz_log_in('admin');
$xmlrpc->bz_call_success('Bug.update', {
ids => [$public_id],
deadline => '2038-01-01',
estimated_time => '10.0',
remaining_time => '5.0',
});
$xmlrpc->bz_call_success('User.logout');
# Populate other fields.
$public_bug->{classification} = 'Unclassified';
$private_bug->{classification} = 'Unclassified';
$private_bug->{groups} = ['QA-Selenium-TEST'];
$public_bug->{groups} = [];
# The user filing $private_bug doesn't have permission to set the status
# or qa_contact, so they differ from normal $public_bug values.
$private_bug->{qa_contact} = $config->{PRIVATE_BUG_USER . '_user_login'};
sub post_success {
my ($call, $t, $rpc) = @_;
is(scalar @{ $call->result->{bugs} }, 1, "Got exactly one bug");
my $bug = $call->result->{bugs}->[0];
if ($t->{user} && $t->{user} eq 'admin') {
ok(exists $bug->{estimated_time} && exists $bug->{remaining_time},
'Admin correctly gets time-tracking fields');
is($bug->{deadline}, '2038-01-01', 'deadline is correct');
cmp_ok($bug->{estimated_time}, '==', '10.0',
'estimated_time is correct');
cmp_ok($bug->{remaining_time}, '==', '5.0',
'remaining_time is correct');
}
else {
ok(!exists $bug->{estimated_time} && !exists $bug->{remaining_time},
'Time-tracking fields are not returned to non-privileged users');
}
if ($t->{user}) {
ok($bug->{update_token}, 'Update token returned for logged-in user');
}
else {
ok(!exists $bug->{update_token},
'Update token not returned for logged-out users');
}
my $expect = $bug->{id} == $private_bug->{id} ? $private_bug : $public_bug;
my @fields = sort keys %$expect;
push(@fields, 'creation_time', 'last_change_time');
$rpc->bz_test_bug(\@fields, $bug, $expect, $t, $creation_time);
}
my @tests = (
@{ bug_tests($public_id, $private_id) },
{ args => { ids => [$public_id],
include_fields => ['id', 'summary', 'groups'] },
test => 'include_fields',
},
{ args => { ids => [$public_id],
exclude_fields => ['assigned_to', 'cf_qa_status'] },
test => 'exclude_fields' },
{ args => { ids => [$public_id],
include_fields => ['id', 'summary', 'groups'],
exclude_fields => ['summary'] },
test => 'exclude_fields overrides include_fields' },
);
foreach my $rpc (@clients) {
$rpc->bz_run_tests(tests => \@tests, method => 'Bug.get',
post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#########################################
# Test for xmlrpc call to Bug.history() #
#########################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use QA::Util;
use QA::Tests qw(STANDARD_BUG_TESTS);
use Test::More tests => 114;
my ($config, @clients) = get_rpc_clients();
sub post_success {
my ($call, $t) = @_;
is(scalar @{ $call->result->{bugs} }, 1, "Got exactly one bug");
isa_ok($call->result->{bugs}->[0]->{history}, 'ARRAY', "Bug's history");
}
foreach my $rpc (@clients) {
$rpc->bz_run_tests(tests => STANDARD_BUG_TESTS,
method => 'Bug.history', post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
##############################################
# Test for xmlrpc call to Bug.legal_values() #
##############################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More tests => 269;
use QA::Util;
my ($config, @clients) = get_rpc_clients();
use constant INVALID_PRODUCT_ID => -1;
use constant INVALID_FIELD_NAME => 'invalid_field';
use constant GLOBAL_FIELDS =>
qw(bug_severity bug_status op_sys priority rep_platform resolution
cf_qa_status cf_single_select);
use constant PRODUCT_FIELDS => qw(version target_milestone component);
my $products = $clients[0]->bz_get_products();
my $public_product = $products->{'Another Product'};
my $private_product = $products->{'QA-Selenium-TEST'};
my @all_tests;
for my $field (GLOBAL_FIELDS) {
push(@all_tests,
{ args => { field => $field },
test => "Logged-out user can get $field values" });
}
for my $field (PRODUCT_FIELDS) {
my @tests = (
{ args => { field => $field },
error => "argument was not set",
test => "$field can't be accessed without a value for 'product'",
},
{ args => { product_id => INVALID_PRODUCT_ID, field => $field },
error => "does not exist",
test => "$field cannot be accessed with an invalid product id",
},
{ args => { product_id => $private_product, field => $field },
error => "you don't have access",
test => "Logged-out user cannot access $field in private product"
},
{ args => { product_id => $public_product, field => $field },
test => "Logged-out user can access $field in a public product",
},
{ user => 'unprivileged',
args => { product_id => $private_product, field => $field },
error => "you don't have access",
test => "Unprivileged user cannot access $field in private product",
},
{ user => 'unprivileged',
args => { product_id => $public_product, field => $field },
test => "Logged-in user can access $field in public product",
},
{ user => 'QA_Selenium_TEST',
args => { product_id => $private_product, field => $field },
test => "Privileged user can access $field in a private product",
},
);
push(@all_tests, @tests);
}
my @extra_tests = (
{ args => { product_id => $private_product, },
error => "requires a field argument",
test => "Passing product_id without 'field' throws an error",
},
{ args => { field => INVALID_FIELD_NAME },
error => "Can't use \"" . INVALID_FIELD_NAME . "\" as a field name",
test => 'Invalid field name'
},
);
push(@all_tests, @extra_tests);
sub post_success {
my ($call) = @_;
cmp_ok(scalar @{ $call->result->{'values'} }, '>', 0,
'Got one or more values');
}
foreach my $rpc (@clients) {
$rpc->bz_run_tests(tests => \@all_tests, method => 'Bug.legal_values',
post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
########################################
# Test for xmlrpc call to Bug.search() #
########################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use QA::Util;
use QA::Tests qw(PRIVATE_BUG_USER);
use DateTime;
use List::MoreUtils qw(uniq);
use Test::More;
use Data::Dumper;
my ($config, @clients) = get_rpc_clients();
plan tests => $config->{test_extensions} ? 531 : 522;
my ($public_bug, $private_bug) = $clients[0]->bz_create_test_bugs('private');
# Add aliases to both bugs
$public_bug->{alias} = random_string(40);
$private_bug->{alias} = random_string(40);
my $alias_tests = [
{ user => 'editbugs',
args => { ids => [ $public_bug->{id} ], alias => $public_bug->{alias} },
test => 'Add alias to public bug' },
{ user => PRIVATE_BUG_USER,
args => { ids => [ $private_bug->{id} ],
cc => { add => [ $config->{'editbugs_user_login'} ] } },
test => 'Add editusers to cc of private bug' },
{ user => 'editbugs',
args => { ids => [ $private_bug->{id} ], alias => $private_bug->{alias} },
test => 'Add alias to private bug' },
{ user => PRIVATE_BUG_USER,
args => { ids => [ $private_bug->{id} ],
cc => { remove => [ $config->{'editbugs_user_login'} ] } },
test => 'Remove editusers from cc of private bug' },
];
$clients[0]->bz_run_tests(tests => $alias_tests, method => 'Bug.update');
my @tests;
foreach my $field (keys %$public_bug) {
next if ($field eq 'cc' or $field eq 'description');
my $test = { args => { $field => $public_bug->{$field} },
test => "Search by $field" };
if ( grep($_ eq $field, qw(alias whiteboard summary)) ) {
$test->{exactly} = 1; $test->{bugs} = 1;
}
push(@tests, $test);
}
push(@tests, (
{ args => { offset => 1 },
test => "Offset without limit fails",
error => 'requires a limit argument',
},
{ args => { alias => $private_bug->{alias} },
test => 'Logged-out cannot find a private_bug by alias',
bugs => 0,
},
{ args => { creation_time => '19700101T00:00:00' },
test => 'Get all bugs by creation time',
},
{ args => { creation_time => '20380101T00:00:00' },
test => 'Get no bugs, by creation time',
bugs => 0,
},
{ args => { last_change_time => '19700101T00:00:00' },
test => 'Get all bugs by last_change_time',
},
{ args => { last_change_time => '20380101T00:00:00' },
test => 'Get no bugs by last_change_time',
bugs => 0,
},
{ args => { reporter => $config->{editbugs_user_login} },
test => 'Search by reporter',
},
{ args => { resolution => '' },
test => 'Search for empty resolution',
},
{ args => { resolution => 'NO_SUCH_RESOLUTION' },
test => 'Search for invalid resolution',
bugs => 0,
},
{ args => { summary => substr($public_bug->{summary}, 0, 50) },
test => 'Search by partial summary',
bugs => 1, exactly => 1
},
{ args => { summary => random_string() . ' ' . random_string() },
test => 'Summary search that returns no results',
bugs => 0,
},
{ args => { summary => [split(/\s/, $public_bug->{summary})] },
test => 'Summary search using multiple terms',
},
{ args => { whiteboard => substr($public_bug->{whiteboard}, 0, 50) },
test => 'Search by partial whiteboard',
bugs => 1, exactly => 1,
},
{ args => { whiteboard => random_string(100) },
test => 'Whiteboard search that returns no results',
bugs => 0,
},
{ args => { whiteboard => [split(/\s/, $public_bug->{whiteboard})] },
test => 'Whiteboard search using multiple terms',
bugs => 1, exactly => 1,
},
{ args => { product => $public_bug->{product},
component => $public_bug->{component},
last_change_time => '19700101T00:00:00' },
test => 'Search by multiple arguments',
},
# Logged-in user who can see private bugs
{ user => PRIVATE_BUG_USER,
args => { alias => [$public_bug->{alias}, $private_bug->{alias}] },
test => 'Search using two aliases (including one private)',
bugs => 2, exactly => 1,
},
{ user => PRIVATE_BUG_USER,
args => { product => [$public_bug->{product}, $private_bug->{product}],
limit => 1 },
test => 'Limit 1',
bugs => 1, exactly => 1,
},
{ user => PRIVATE_BUG_USER,
args => { product => [$public_bug->{product}, $private_bug->{product}],
limit => 1, offset => 1 },
test => 'Limit 1 Offset 1',
bugs => 1, exactly => 1,
},
# include_fields ane exclude_fields
{ args => { id => $public_bug->{id},
include_fields => ['id', 'alias', 'summary', 'groups'] },
test => 'include_fields',
},
{ args => { id => $public_bug->{id},
exclude_fields => ['assigned_to', 'cf_qa_status'] },
test => 'exclude_fields' },
{ args => { id => $public_bug->{id},
include_fields => ['id', 'alias', 'summary', 'groups'],
exclude_fields => ['summary'] },
test => 'exclude_fields overrides include_fields' },
));
push(@tests,
{ args => { votes => 1 },
test => 'Search by votes',
bugs => -1, # We don't care how many it returns, for now.
}) if $config->{test_extensions};
sub post_success {
my ($call, $t) = @_;
my $bugs = $call->result->{bugs};
my $expected_count = $t->{bugs};
$expected_count = 1 if !defined $expected_count;
if ($expected_count) {
my $operator = $t->{exactly} ? '==' : '>=';
cmp_ok(scalar @$bugs, $operator, $expected_count,
'The right number of bugs are returned');
unless ($t->{user} and $t->{user} eq PRIVATE_BUG_USER) {
ok(!grep($_->{alias} && $_->{alias} eq $private_bug->{alias}, @$bugs),
'Result does not contain the private bug');
}
my @include = @{ $t->{args}->{include_fields} || [] };
my @exclude = @{ $t->{args}->{exclude_fields} || [] };
if (@include or @exclude) {
my @check_fields = uniq (keys %$public_bug, @include);
foreach my $field (sort @check_fields) {
next if $field eq 'description';
if ((@include and !grep { $_ eq $field } @include )
or (@exclude and grep { $_ eq $field } @exclude))
{
ok(!exists $bugs->[0]->{$field}, "$field is not included")
or diag Dumper($bugs);
}
else {
ok(exists $bugs->[0]->{$field}, "$field is included");
}
}
}
}
else {
is(scalar @$bugs, 0, 'No bugs returned');
}
}
foreach my $rpc (@clients) {
$rpc->bz_run_tests(tests => \@tests,
method => 'Bug.search', post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Data::Dumper;
use QA::Util;
use QA::Tests qw(PRIVATE_BUG_USER STANDARD_BUG_TESTS);
use Storable qw(dclone);
use Test::More tests => 937;
use constant NONEXISTENT_BUG => 12_000_000;
###############
# Subroutines #
###############
# We have to generate different values for each RPC client, so we
# have a function to generate the tests for each client.
sub get_tests {
my ($config, $rpc) = @_;
# update doesn't support logged-out users.
my @tests = grep { $_->{user} } @{ STANDARD_BUG_TESTS() };
my ($public_bug, $second_bug) = $rpc->bz_create_test_bugs();
my ($public_id, $second_id) = ($public_bug->{id}, $second_bug->{id});
# Add aliases to both bugs
$public_bug->{alias} = random_string(40);
$second_bug->{alias} = random_string(40);
my $alias_tests = [
{ user => 'editbugs',
args => { ids => [ $public_id ], alias => $public_bug->{alias} },
test => 'Add alias to public bug' },
{ user => 'editbugs',
args => { ids => [ $second_id ], alias => $second_bug->{alias} },
test => 'Add alias to second bug' },
];
$rpc->bz_run_tests(tests => $alias_tests, method => 'Bug.update');
my $comment_call = $rpc->bz_call_success(
'Bug.comments', { ids => [$public_id, $second_id] });
$public_bug->{comment} =
$comment_call->result->{bugs}->{$public_id}->{comments}->[0];
$second_bug->{comment} =
$comment_call->result->{bugs}->{$second_id}->{comments}->[0];
push(@tests, (
{ args => { ids => [$public_id] },
error => 'You must log in',
test => 'Logged-out users cannot call update' },
# FIXME We need a permissions test for canedit, but it's so uncommonly
# used that it's not a high priority.
));
my %valid = valid_values($config, $public_bug, $second_bug);
my $valid_value_tests = valid_values_to_tests(\%valid, $public_bug);
push(@tests, @$valid_value_tests);
my %invalid = invalid_values($public_bug, $second_bug);
my $invalid_value_tests = invalid_values_to_tests(\%invalid, $public_bug);
push(@tests, @$invalid_value_tests);
return \@tests;
}
sub valid_values {
my ($config, $public_bug, $second_bug) = @_;
my $admin = $config->{'admin_user_login'};
my $second_id = $second_bug->{id};
my $comment_id = $public_bug->{comment}->{id};
my $bug_uri = $config->{browser_url} . '/'
. $config->{bugzilla_installation} . '/show_bug.cgi?id=';
my %values = (
alias => [
{ value => random_string(20) },
],
assigned_to => [
{ value => $config->{'unprivileged_user_login'} }
],
blocks => [
{ value => { set => [$second_id] },
added => $second_id,
test => 'set to second bug' },
{ value => { remove => [$second_id] },
added => '', removed => $second_id,
test => 'remove second bug' },
{ value => { add => [$second_id] },
added => $second_id, removed => '',
test => 'add second bug' },
{ value => { set => [] },
added => '', removed => $second_id,
test => 'set to nothing' },
],
cc => [
{ value => { add => [$admin] },
added => $admin, removed => '',
test => 'add admin' },
{ value => { remove => [$admin] },
added => '', removed => $admin,
test => 'remove admin' },
{ value => { remove => [$admin] },
test => "removing user who isn't on the list works",
no_changes => 1 },
],
is_cc_accessible => [
{ value => 0, test => 'set to 0' },
{ value => 1, test => 'set to 1' },
],
comment => [
{ value => { body => random_string(100) }, test => 'public' },
{ value => { body => random_string(100), is_private => 1 },
user => PRIVATE_BUG_USER, test => 'private' },
],
comment_is_private => [
{ value => { $comment_id => 1 },
user => PRIVATE_BUG_USER, test => 'make description private' },
{ value => { $comment_id => 0 },
user => PRIVATE_BUG_USER, test => 'make description public' },
],
component => [
{ value => 'c2' }
],
deadline => [
{ value => '2037-01-01' },
{ value => '', removed => '2037-01-01', test => 'remove' },
],
dupe_of => [
{ value => $second_id },
],
estimated_time => [
{ value => '10.0' },
{ value => '0.0', removed => '10.0', test => 'set to zero' },
],
groups => [
{ value => { add => ['Master'] },
user => 'admin', added => 'Master', test => 'add Master' },
{ value => { remove => ['Master'] },
user => 'admin', added => '', removed => 'Master',
test => 'remove Master' },
],
keywords => [
{ value => { add => ['test-keyword-1'] },
test => 'add one', added => 'test-keyword-1' },
{ value => { set => ['test-keyword-1', 'test-keyword-2'] },
test => 'set two', added => 'test-keyword-2' },
{ value => { remove => ['test-keyword-1'] },
removed => 'test-keyword-1', added => '',
test => 'remove one' },
{ value => { set => [] },
removed => 'test-keyword-2', added => '',
test => 'set to empty' },
{ value => { remove => ['test-keyword-2'] },
test => 'removing removed keyword does nothing',
no_changes => 1 },
],
op_sys => [
{ value => 'All' },
],
platform => [
{ value => 'All' },
],
priority => [
{ value => 'Normal' },
],
product => [
{ value => 'C2 Forever',
extra => {
component => 'Helium', version => 'unspecified',
target_milestone => '---',
},
test => 'move to C2 Forever'
},
# This also tests that the extra fields transfer over properly
# when they have identical names in both products.
{ value => $public_bug->{product},
extra => { component => $public_bug->{component} },
test => 'move back to original product' },
],
qa_contact => [
{ value => $admin },
{ value => '', test => 'set blank', removed => $admin },
# Reset to the original so that reset_qa_contact can also be tested.
{ value => $public_bug->{qa_contact} },
],
remaining_time => [
{ value => '1000.50' },
{ value => 0 },
],
reset_assigned_to => [
{ value => 1, field => 'assigned_to',
added => $config->{permanent_user} },
],
reset_qa_contact => [
{ value => 1, field => 'qa_contact', added => '' },
],
resolution => [
{ value => 'FIXED', extra => { status => 'RESOLVED' },
test => 'to RESOLVED FIXED' },
{ value => 'INVALID', test => 'just resolution' },
],
see_also => [
{ value => { add => [$bug_uri . $second_id] },
added => $bug_uri . $second_id, removed => '',
test => 'add local bug URI' },
{ value => { remove => [$bug_uri . $second_id] },
removed => $bug_uri . $second_id, added => '',
test => 'remove local bug URI' },
{ value => { remove => ['http://landfill.bugzilla.org/bugzilla-tip/show_bug.cgi?id=1'] },
no_changes => 1,
test => 'removing non-existent URI works' },
{ value => { add => [''] },
no_changes => 1,
test => 'adding an empty string to see_also does nothing' },
{ value => { add => [undef] },
no_changes => 1,
test => 'adding a null to see_also does nothing' },
],
status => [
# At this point, due to previous tests, the status is RESOLVED,
# so changing to CONFIRMED is our only real option if we want to
# test a simple open status.
{ value => 'CONFIRMED' },
],
severity => [
{ value => 'critical' },
],
summary => [
{ value => random_string(100) },
],
target_milestone => [
{ value => 'AnotherMS2' },
],
url => [
{ value => 'http://' . random_string(20) . '/' },
],
version => [
{ value => 'Another2' },
],
whiteboard => [
{ value => random_string(1000) },
],
work_time => [
# FIXME: work_time really needs to start showing up in the changes
# hash.
{ value => '1.2', no_changes => 1 },
{ value => '-1.2', test => 'negative value', no_changes => 1 },
],
);
$values{depends_on} = $values{blocks};
$values{is_creator_accessible} = $values{is_cc_accessible};
return %values;
};
sub valid_values_to_tests {
my ($valid_values, $public_bug) = @_;
my @tests;
foreach my $field (sort keys %$valid_values) {
my @tests_valid = @{ $valid_values->{$field} };
foreach my $item (@tests_valid) {
my $desc = $item->{test} || 'valid value';
my %args = (
ids => [$public_bug->{id}],
$field => $item->{value},
%{ $item->{extra} || {} },
);
my %test = ( user => 'editbugs', args => \%args, field => $field,
test => "$field: $desc" );
foreach my $item_field (qw(no_changes added removed field user)) {
next if !exists $item->{$item_field};
$test{$item_field} = $item->{$item_field};
}
push(@tests, \%test);
}
}
return \@tests;
}
sub invalid_values {
my ($public_bug, $second_bug) = @_;
my $public_id = $public_bug->{id};
my $second_id = $second_bug->{id};
my $comment_id = $public_bug->{comment}->{id};
my $second_comment_id = $second_bug->{comment}->{id};
my %values = (
alias => [
{ value => random_string(41),
error => 'aliases cannot be longer than',
test => 'alias cannot be too long' },
{ value => $second_bug->{alias},
error => 'has already taken the alias',
test => 'duplicate alias fails' },
{ value => 123456,
error => 'at least one letter',
test => 'numeric alias fails' },
{ value => random_string(20), ids => [$public_id, $second_id],
error => 'aliases when modifying multiple',
test => 'setting alias on multiple bugs fails' },
],
assigned_to => [
{ value => random_string(20),
error => 'There is no user named',
test => 'changing assigned_to to invalid user fails' },
{ value => '',
error => 'you must provide an address for the new assignee',
test => 'empty assigned_to fails' },
# FIXME Also check strict_isolation at some point in the future,
# perhaps.
],
blocks => [
{ value => { add => [NONEXISTENT_BUG] },
error => 'does not exist',
test => 'Non-existent bug number fails in deps' },
{ value => { add => [$public_id] },
error => 'block itself or depend on itself',
test => "can't add this bug itself in a dep field" },
# FIXME Could use strict_isolation checks at some point.
# FIXME Could use a dependency_loop_multi test.
],
cc => [
{ value => { add => [random_string(20)] },
error => 'There is no user named',
test => 'adding invalid user to cc fails' },
{ value => { remove => [random_string(20)] },
error => 'There is no user named',
test => 'removing invalid user from cc fails' },
],
comment => [
{ value => { body => random_string(100_000) },
error => 'cannot be longer',
test => 'comment too long' },
{ value => { body => random_string(100), is_private => 1 },
error => 'comments or attachments as private',
test => 'normal user cannot add private comments' },
],
comment_is_private => [
{ value => { $comment_id => 1 },
error => 'comments or attachments as private',
test => 'normal user cannot make a comment private' },
{ value => { $second_comment_id => 1 },
error => 'You tried to modify the privacy of comment',
user => PRIVATE_BUG_USER,
test => 'cannot change privacy on a comment on another bug' },
],
component => [
{ value => '',
error => 'you must first choose a component',
test => 'empty component fails' },
{ value => random_string(20),
error => 'There is no component named',
test => 'invalid component fails' },
],
deadline => [
{ value => random_string(20),
error => 'is not a legal date',
test => 'Non-date fails in deadline' },
{ value => '2037',
error => 'is not a legal date',
test => 'year alone fails in deadline' },
],
dupe_of => [
{ value => undef,
error => 'dup_id was not defined',
test => 'undefined dupe_of fails' },
{ value => NONEXISTENT_BUG,
error => 'does not exist',
test => 'Cannot dup to a nonexistent bug' },
{ value => $public_id,
error => 'as a duplicate of itself',
test => 'Cannot dup bug to itself' },
],
estimated_time => [
{ value => -1,
error => 'less than the minimum allowable value',
test => 'negative estimated_time fails' },
{ value => 100_000_000,
error => 'more than the maximum allowable value',
test => 'too-large estimated_time fails' },
{ value => random_string(20),
error => 'is not a numeric value',
test => 'non-numeric estimated_time fails' },
# We use PRIVATE_BUG_USER because he can modify the bug, but
# can't change time-tracking fields.
{ value => '100', user => PRIVATE_BUG_USER,
error => 'only a user with the required permissions',
test => 'non-timetracker can not set estimated_time' },
],
groups => [
{ value => { add => ['Master'] },
error => 'either this group does not exist, or you are not allowed to restrict bugs to this group',
test => "adding group we don't have access to but is valid fails" },
{ value => { add => ['QA-Selenium-TEST'] },
error => 'either this group does not exist, or you are not allowed to restrict bugs to this group',
test => 'adding valid group that is not in this product fails' },
{ value => { add => [random_string(20)] },
error => 'either this group does not exist, or you are not allowed to restrict bugs to this group',
test => 'adding non-existent group fails' },
{ value => { remove => [random_string(20)] },
error => 'either this group does not exist, or you are not allowed to remove bugs from this group',
test => 'removing non-existent group fails' },
],
keywords => [
{ value => { add => [random_string(20)] },
error => 'See the list of available keywords',
test => 'adding invalid keyword fails' },
{ value => { remove => [random_string(20)] },
error => 'See the list of available keywords',
test => 'removing invalid keyword fails' },
{ value => { set => [random_string(20)] },
error => 'See the list of available keywords',
test => 'setting invalid keyword fails' },
],
op_sys => [
{ value => random_string(20),
error => 'There is no',
test => 'invalid op_sys fails' },
{ value => '',
error => 'You must select/enter',
test => 'blank op_sys fails' },
],
product => [
{ value => random_string(60),
error => "does not exist or you aren't authorized",
test => 'invalid product fails' },
{ value => '',
error => 'You must select/enter a product',
test => 'moving to blank product fails' },
{ value => 'TestProduct',
error => 'There is no component named',
test => 'moving products without other fields fails' },
{ value => 'QA-Selenium-TEST',
extra => { component => 'QA-Selenium-TEST' },
error => "does not exist or you aren't authorized",
test => 'moving to inaccessible product fails' },
{ value => 'QA Entry Only',
error => "does not exist or you aren't authorized",
test => 'moving to product where ENTRY is denied fails' },
],
qa_contact => [
{ value => random_string(20),
error => 'There is no user named',
test => 'changing qa_contact to invalid user fails' },
],
remaining_time => [
{ value => -1,
error => 'less than the minimum allowable value',
test => 'negative remaining_time fails' },
{ value => 100_000_000,
error => 'more than the maximum allowable value',
test => 'too-large remaining_time fails' },
{ value => random_string(20),
error => 'is not a numeric value',
test => 'non-numeric remaining_time fails' },
# We use PRIVATE_BUG_USER because he can modify the bug, but
# can't change time-tracking fields.
{ value => '100', user => PRIVATE_BUG_USER,
error => 'only a user with the required permissions',
test => 'non-timetracker can not set remaining_time' },
],
# We do all the failing resolution tests on the second bug,
# because we want to be sure that we're starting from an open
# status.
resolution => [
{ value => random_string(20), ids => [$second_id],
extra => { status => 'RESOLVED' },
error => 'There is no Resolution named',
test => 'invalid resolution fails' },
{ value => 'FIXED', ids => [$second_id],
error => 'You cannot set a resolution for open bugs',
test => 'setting resolution on open bug fails' },
{ value => 'DUPLICATE', ids => [$second_id],
extra => { status => 'RESOLVED' },
error => 'id to mark this bug as a duplicate',
test => 'setting DUPLICATE without dup_id fails' },
{ value => '', ids => [$second_id],
extra => { status => 'RESOLVED' },
error => 'A valid resolution is required',
test => 'blank resolution fails with closed status' },
],
see_also => [
{ value => { add => [random_string(20)] },
error => 'is not a valid bug number nor an alias',
test => 'random string fails in see_also' },
{ value => { add => ['http://landfill.bugzilla.org/'] },
error => 'See Also URLs should point to one of',
test => 'no show_bug.cgi in see_also URI' },
],
status => [
{ value => random_string(20),
error => 'There is no status named',
test => 'invalid status fails' },
{ value => '',
error => 'You must select/enter a status',
test => 'blank status fails' },
# We use the second bug for this because we can guarantee that
# it is open.
{ value => 'VERIFIED', ids => [$second_id],
extra => { resolution => 'FIXED' },
error => 'You are not allowed to change the bug status from',
test => 'invalid transition fails' },
],
summary => [
{ value => random_string(300),
error => 'The text you entered in the Summary field is too long',
test => 'too-long summary fails' },
{ value => '',
error => 'You must enter a summary for this bug',
test => 'blank summary fails' },
],
work_time => [
{ value => 100_000_000,
error => 'more than the maximum allowable value',
test => 'too-large work_time fails' },
{ value => random_string(20),
error => 'is not a numeric value',
test => 'non-numeric work_time fails' },
# We use PRIVATE_BUG_USER because he can modify the bug, but
# can't change time-tracking fields.
{ value => '10', user => PRIVATE_BUG_USER,
error => 'only a user with the required permissions',
test => 'non-timetracker can not set work_time' },
],
);
$values{depends_on} = $values{blocks};
foreach my $field (qw(platform priority severity target_milestone version))
{
my $tests = dclone($values{op_sys});
foreach my $test (@$tests) {
$test->{test} =~ s/op_sys/$field/g;
}
$values{$field} = $tests;
}
return %values;
}
sub invalid_values_to_tests {
my ($invalid_values, $public_bug) = @_;
my @tests;
foreach my $field (sort keys %$invalid_values) {
my @tests_invalid = @{ $invalid_values->{$field} };
foreach my $item (@tests_invalid) {
my %args = (
ids => $item->{ids} || [$public_bug->{id}],
$field => $item->{value},
%{ $item->{extra} || {} },
);
push(@tests, { user => $item->{user} || 'editbugs',
args => \%args,
error => $item->{error},
test => $item->{test} });
}
}
return \@tests;
}
###############
# Main Script #
###############
my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients();
$jsonrpc_get->bz_call_fail('Bug.update',
{ ids => ['public_bug'] },
'must use HTTP POST', 'update fails over GET');
sub post_success {
my ($call, $t, $rpc) = @_;
return if $t->{no_changes};
my $field = $t->{field};
return if !$field;
my @bugs = @{ $call->result->{bugs} };
foreach my $bug (@bugs) {
if ($field =~ /^comment/) {
_check_comment($bug, $field, $t, $rpc);
}
else {
_check_changes($bug, $field, $t);
}
}
}
sub _check_changes {
my ($bug, $field, $t) = @_;
my $changes = $bug->{changes}->{$field};
ok(defined $changes, "$field was changed")
or diag Dumper($bug, $t);
my $new_value = $t->{added};
$new_value = $t->{args}->{$field} if !defined $new_value;
_test_value($changes->{added}, $new_value, $field, 'added');
if (defined $t->{removed}) {
_test_value($changes->{removed}, $t->{removed}, $field, 'removed');
}
}
sub _test_value {
my ($got, $expected, $field, $type) = @_;
if ($field eq 'estimated_time' or $field eq 'remaining_time') {
cmp_ok($got, '==', $expected, "$field: $type is correct");
}
else {
is($got, $expected, "$field: $type is correct");
}
}
sub _check_comment {
my ($bug, $field, $t, $rpc) = @_;
my $bug_id = $bug->{id};
my $call = $rpc->bz_call_success('Bug.comments', { ids => [$bug_id] });
my $comments = $call->result->{bugs}->{$bug_id}->{comments};
if ($field eq 'comment_is_private') {
my $first_private = $comments->[0]->{is_private};
my ($expected) = values %{ $t->{args}->{comment_is_private} };
cmp_ok($first_private, '==', $expected,
'description privacy is correct');
}
else {
my $last_comment = $comments->[-1];
my $expected = $t->{args}->{comment}->{body};
is($last_comment->{text}, $expected, 'comment added correctly');
}
}
foreach my $rpc ($jsonrpc, $xmlrpc) {
$rpc->bz_run_tests(tests => get_tests($config, $rpc),
method => 'Bug.update', post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#################################################
# Test for xmlrpc call to Bug.update_see_also() #
#################################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use QA::Util;
use QA::Tests qw(PRIVATE_BUG_USER STANDARD_BUG_TESTS);
use Test::More tests => 117;
my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients();
my $bug_url = 'http://landfill.bugzilla.org/bugzilla-tip/show_bug.cgi?id=100';
# update_see_also doesn't support logged-out users.
my @tests = grep { $_->{user} } @{ STANDARD_BUG_TESTS() };
foreach my $t (@tests) {
$t->{args}->{add} = $t->{args}->{remove} = [];
}
push(@tests, (
{ user => 'unprivileged',
args => { ids => ['public_bug'], add => [$bug_url] },
error => 'only the assignee or reporter of the bug, or a user',
test => 'Unprivileged user cannot add a URL to a bug',
},
{ user => 'admin',
args => { ids => ['public_bug'], add => ['asdfasdfasdf'] },
error => 'asdf',
test => 'Admin cannot add an invalid URL',
},
{ user => 'admin',
args => { ids => ['public_bug'], remove => ['asdfasdfasdf'] },
test => 'Invalid URL silently ignored',
},
{ user => 'admin',
args => { ids => ['public_bug'], add => [$bug_url] },
test => 'Admin can add a URL to a public bug',
},
{ user => 'unprivileged',
args => { ids => ['public_bug'], remove => [$bug_url] },
error => 'only the assignee or reporter of the bug, or a user',
test => 'Unprivileged user cannot remove a URL from a bug',
},
{ user => 'admin',
args => { ids => ['public_bug'], remove => [$bug_url] },
test => 'Admin can remove a URL from a public bug',
},
{ user => PRIVATE_BUG_USER,
args => { ids => ['private_bug'], add => [$bug_url] },
test => PRIVATE_BUG_USER . ' can add a URL to a private bug',
},
{ user => PRIVATE_BUG_USER,
args => { ids => ['private_bug'], remove => [$bug_url] },
test => PRIVATE_BUG_USER . ' can remove a URL from a private bug',
},
));
sub post_success {
my ($call, $t) = @_;
isa_ok($call->result->{changes}, 'HASH', "Changes");
}
$jsonrpc_get->bz_call_fail('Bug.update_see_also',
{ ids => ['public_bug'], add => [$bug_url] },
'must use HTTP POST', 'update_see_also fails over GET');
foreach my $rpc ($jsonrpc, $xmlrpc) {
$rpc->bz_run_tests(tests => \@tests, method => 'Bug.update_see_also',
post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
##################################################
# Test for xmlrpc call functions in Bugzilla.pm #
##################################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More tests => 11 * 3;
use QA::Util;
my ($config, @clients) = get_rpc_clients();
foreach my $rpc (@clients) {
my $vers_call = $rpc->bz_call_success('Bugzilla.version');
my $version = $vers_call->result->{version};
ok($version, "Bugzilla.version returns $version");
my $tz_call = $rpc->bz_call_success('Bugzilla.timezone');
my $tz = $tz_call->result->{timezone};
ok($tz, "Bugzilla.timezone retuns $tz");
my $ext_call = $rpc->bz_call_success('Bugzilla.extensions');
my $extensions = $ext_call->result->{extensions};
isa_ok($extensions, 'HASH', 'extensions');
# There is always at least the QA extension enabled.
my $cmp = $config->{test_extensions} ? '>' : '==';
my @ext_names = keys %$extensions;
my $desc = scalar(@ext_names) . ' extension(s) returned: ' . join(', ', @ext_names);
cmp_ok(scalar(@ext_names), $cmp, 1, $desc);
ok(grep($_ eq 'QA', @ext_names), 'The QA extension is enabled');
my $time_call = $rpc->bz_call_success('Bugzilla.time');
my $time_result = $time_call->result;
foreach my $type (qw(db_time web_time)) {
cmp_ok($time_result->{$type}, '=~', $rpc->DATETIME_REGEX,
"Bugzilla.time returns a datetime for $type");
}
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
##########################################
# Test for xmlrpc call to Group.create() #
##########################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More tests => 77;
use QA::Util;
use constant DESCRIPTION => 'Group created by Group.create';
sub post_success {
my $call = shift;
my $gid = $call->result->{id};
ok($gid, "Got a non-zero group ID: $gid");
}
my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients();
my @tests = (
{ args => { name => random_string(20), description => DESCRIPTION },
error => 'You must log in',
test => 'Logged-out user cannot call Group.create',
},
{ user => 'unprivileged',
args => { name => random_string(20), description => DESCRIPTION },
error => 'you are not authorized',
test => 'Unprivileged user cannot call Group.create',
},
{ user => 'admin',
args => { description => DESCRIPTION },
error => 'You must enter a name',
test => 'Missing name to Group.create',
},
{ user => 'admin',
args => { name => random_string(20) },
error => 'You must enter a description',
test => 'Missing description to Group.create',
},
{ user => 'admin',
args => { name => '', description => DESCRIPTION },
error => 'You must enter a name',
test => 'Name to Group.create cannot be empty',
},
{ user => 'admin',
args => { name => random_string(20), description => '' },
error => 'You must enter a description',
test => 'Description to Group.create cannot be empty',
},
{ user => 'admin',
args => { name => 'canconfirm', description => DESCRIPTION },
error => 'already exists',
test => 'Name to Group.create already exists',
},
{ user => 'admin',
args => { name => 'caNConFIrm', description => DESCRIPTION },
error => 'already exists',
test => 'Name to Group.create already exists but with a different case',
},
{ user => 'admin',
args => { name => random_string(20), description => DESCRIPTION,
user_regexp => '\\'},
error => 'The regular expression you entered is invalid',
test => 'The regular expression passed to Group.create is invalid',
},
);
$jsonrpc_get->bz_call_fail('Group.create',
{ name => random_string(20), description => 'Created with JSON-RPC via GET' },
'must use HTTP POST', 'Group.create fails over GET');
foreach my $rpc ($xmlrpc, $jsonrpc) {
# Tests which work must be called from here,
# to avoid creating twice the same group.
my @all_tests = (@tests,
{ user => 'admin',
args => { name => random_string(20), description => DESCRIPTION },
test => 'Passing the name and description only works',
},
{ user => 'admin',
args => { name => random_string(20), description => DESCRIPTION,
user_regexp => '\@foo.com$', is_active => 1,
icon_url => 'http://www.bugzilla.org/favicon.ico' },
test => 'Passing all arguments works',
},
);
$rpc->bz_run_tests(tests => \@all_tests, method => 'Group.create',
post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More tests => 85;
use QA::Util;
my $jsonrpc_get = QA::Util::get_jsonrpc_client('GET');
my @chars = (0..9, 'A'..'Z', 'a'..'z', '_[].');
our @tests = (
{ args => { callback => join('', @chars) },
test => 'callback accepts all legal characters.' },
);
foreach my $char (qw(! ~ ` @ $ % ^ & * - + = { } ; : ' " < > / ? |),
'(', ')', '\\', '#', ',')
{
push(@tests,
{ args => { callback => "a$char" },
error => "as your 'callback' parameter",
test => "$char is not valid in callback" });
}
$jsonrpc_get->bz_run_tests(method => 'Bugzilla.version', tests => \@tests);
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
############################################
# Test for xmlrpc call to Product.create() #
############################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More tests => 121;
use QA::Util;
use constant DESCRIPTION => 'Product created by Product.create';
use constant PROD_VERSION => 'unspecified';
sub post_success {
my ($call, $test, $self) = @_;
my $args = $test->{args};
my $prod_id = $call->result->{id};
ok($prod_id, "Got a non-zero product ID: $prod_id");
$call = $self->bz_call_success("Product.get", {ids => [$prod_id]});
my $product = $call->result->{products}->[0];
my $prod_name = $product->{name};
my $is_active = defined $args->{is_open} ? $args->{is_open} : 1;
ok($product->{is_active} == $is_active,
"Product $prod_name has the correct value for is_active/is_open: $is_active");
my $has_unco = defined $args->{has_unconfirmed} ? $args->{has_unconfirmed} : 1;
ok($product->{has_unconfirmed} == $has_unco,
"Product $prod_name has the correct value for has_unconfirmed: $has_unco");
}
my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients();
my @tests = (
{ args => { name => random_string(20), version => PROD_VERSION,
description => DESCRIPTION },
error => 'You must log in',
test => 'Logged-out user cannot call Product.create',
},
{ user => 'unprivileged',
args => { name => random_string(20), version => PROD_VERSION,
description => DESCRIPTION },
error => 'you are not authorized',
test => 'Unprivileged user cannot call Product.create',
},
{ user => 'admin',
args => { version => PROD_VERSION, description => DESCRIPTION },
error => 'You must enter a name',
test => 'Missing name to Product.create',
},
{ user => 'admin',
args => { name => random_string(20), version => PROD_VERSION },
error => 'You must enter a description',
test => 'Missing description to Product.create',
},
{ user => 'admin',
args => { name => random_string(20), description => DESCRIPTION },
error => 'You must enter a valid version',
test => 'Missing version to Product.create',
},
{ user => 'admin',
args => { name => '', version => PROD_VERSION, description => DESCRIPTION },
error => 'You must enter a name',
test => 'Name to Product.create cannot be empty',
},
{ user => 'admin',
args => { name => random_string(20), version => PROD_VERSION, description => '' },
error => 'You must enter a description',
test => 'Description to Product.create cannot be empty',
},
{ user => 'admin',
args => { name => random_string(20), version => '', description => DESCRIPTION },
error => 'You must enter a valid version',
test => 'Version to Product.create cannot be empty',
},
{ user => 'admin',
args => { name => random_string(20000), version => PROD_VERSION,
description => DESCRIPTION },
error => 'The name of a product is limited',
test => 'Name to Product.create too long',
},
{ user => 'admin',
args => { name => 'Another Product', version => PROD_VERSION,
description => DESCRIPTION },
error => 'already exists',
test => 'Name to Product.create already exists',
},
{ user => 'admin',
args => { name => 'aNoThEr Product', version => PROD_VERSION,
description => DESCRIPTION },
error => 'differs from existing product',
test => 'Name to Product.create already exists but with a different case',
},
);
# FIXME - Should be: if (classifications enabled).
# But there is currently now way to query the value of a parameter via WS.
if (0) {
push(@tests,
{ user => 'admin',
args => { name => random_string(20), version => PROD_VERSION,
description => DESCRIPTION, has_unconfirmed => 1,
classification => '', default_milestone => '2.0',
is_open => 1, create_series => 1 },
error => 'You must select/enter a classification',
test => 'Passing an empty classification to Product.create fails',
},
{ user => 'admin',
args => { name => random_string(20), version => PROD_VERSION,
description => DESCRIPTION, has_unconfirmed => 1,
classification => random_string(10), default_milestone => '2.0',
is_open => 1, create_series => 1 },
error => 'You must select/enter a classification',
test => 'Passing an invalid classification to Product.create fails',
},
)
}
$jsonrpc_get->bz_call_fail('Product.create',
{ name => random_string(20), version => PROD_VERSION,
description => 'Created with JSON-RPC via GET' },
'must use HTTP POST', 'Product.create fails over GET');
foreach my $rpc ($xmlrpc, $jsonrpc) {
# Tests which work must be called from here,
# to avoid creating twice the same product.
my @all_tests = (@tests,
{ user => 'admin',
args => { name => random_string(20), version => PROD_VERSION,
description => DESCRIPTION },
test => 'Passing the name, description and version only works',
},
{ user => 'admin',
args => { name => random_string(20), version => PROD_VERSION,
description => DESCRIPTION, has_unconfirmed => 1,
classification => 'Class2_QA', default_milestone => '2.0',
is_open => 1, create_series => 1 },
test => 'Passing all arguments works',
},
{ user => 'admin',
args => { name => random_string(20), version => PROD_VERSION,
description => DESCRIPTION, has_unconfirmed => 0,
classification => 'Class2_QA', default_milestone => '2.0',
is_open => 0, create_series => 0 },
test => 'Passing null values works',
},
{ user => 'admin',
args => { name => random_string(20), version => PROD_VERSION,
description => DESCRIPTION, has_unconfirmed => 1,
classification => 'Class2_QA', default_milestone => '',
is_open => 1, create_series => 1 },
test => 'Passing an empty default milestone works (falls back to "---")',
},
);
$rpc->bz_run_tests(tests => \@all_tests, method => 'Product.create',
post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
########################################
# Test for xmlrpc calls to: #
# Product.get_selectable_products() #
# Product.get_enterable_products() #
# Product.get_accessible_products() #
# Product.get() #
########################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Test::More tests => 134;
use QA::Util;
my ($config, @clients) = get_rpc_clients();
my $products = $clients[0]->bz_get_products();
my $public = $products->{'Another Product'};
my $private = $products->{'QA-Selenium-TEST'};
my $no_entry = $products->{'QA Entry Only'};
my $no_search = $products->{'QA Search Only'};
my %id_map = reverse %$products;
my $tests = {
'QA_Selenium_TEST' => {
selectable => [$public, $private, $no_entry, $no_search],
enterable => [$public, $private, $no_entry, $no_search],
accessible => [$public, $private, $no_entry, $no_search],
},
'unprivileged' => {
selectable => [$public, $no_entry],
not_selectable => $no_search,
enterable => [$public, $no_search],
not_enterable => $no_entry,
accessible => [$public, $no_entry, $no_search],
not_accessible => $private,
},
'' => {
selectable => [$public, $no_entry],
not_selectable => $no_search,
enterable => [$public, $no_search],
not_enterable => $no_entry,
accessible => [$public, $no_entry, $no_search],
not_accessible => $private,
},
};
foreach my $rpc (@clients) {
foreach my $user (keys %$tests) {
my @selectable = @{ $tests->{$user}->{selectable} };
my @enterable = @{ $tests->{$user}->{enterable} };
my @accessible = @{ $tests->{$user}->{accessible} };
my $not_selectable = $tests->{$user}->{not_selectable};
my $not_enterable = $tests->{$user}->{not_enterable};
my $not_accessible = $tests->{$user}->{not_accessible};
$rpc->bz_log_in($user) if $user;
$user ||= "Logged-out user";
my $select_call =
$rpc->bz_call_success('Product.get_selectable_products');
my $select_ids = $select_call->result->{ids};
foreach my $id (@selectable) {
ok(grep($_ == $id, @$select_ids),
"$user can select " . $id_map{$id});
}
if ($not_selectable) {
ok(!grep($_ == $not_selectable, @$select_ids),
"$user cannot select " . $id_map{$not_selectable});
}
my $enter_call =
$rpc->bz_call_success('Product.get_enterable_products');
my $enter_ids = $enter_call->result->{ids};
foreach my $id (@enterable) {
ok(grep($_ == $id, @$enter_ids), "$user can enter " . $id_map{$id});
}
if ($not_enterable) {
ok(!grep($_ == $not_enterable, @$enter_ids),
"$user cannot enter " . $id_map{$not_enterable});
}
my $access_call =
$rpc->bz_call_success('Product.get_accessible_products');
my $get_call = $rpc->bz_call_success('Product.get',
{ ids => \@accessible });
my $products = $get_call->result->{products};
my $expected_count = scalar @accessible;
cmp_ok(scalar @$products, '==', $expected_count,
"Product.get gets all $expected_count accessible products"
. " for $user.");
if ($not_accessible) {
my $no_access_call = $rpc->bz_call_success(
'Product.get', { ids => [$not_accessible] });
ok(!scalar @{ $no_access_call->result->{products} },
"$user gets 0 products when asking for "
. $id_map{$not_accessible});
}
$rpc->bz_call_success('User.logout') if $user ne "Logged-out user";
}
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#########################################
# Test for xmlrpc call to User.Create() #
#########################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use QA::Util;
use Test::More tests => 75;
my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients();
use constant NEW_PASSWORD => 'password';
use constant NEW_FULLNAME => 'WebService Created User';
use constant PASSWORD_TOO_SHORT => 'a';
# These are the characters that are actually invalid per RFC.
use constant INVALID_EMAIL => '()[]\;:,<>@webservice.test';
sub new_login {
return 'created_' . random_string(@_) . '@webservice.test';
}
sub post_success {
my ($call) = @_;
ok($call->result->{id}, "Got a non-zero user id");
}
$jsonrpc_get->bz_call_fail('User.create',
{ email => new_login(), full_name => NEW_FULLNAME,
password => '*' },
'must use HTTP POST', 'User.create fails over GET');
# We have to wrap @tests in the foreach, because we want a different
# login for each user, separately for each RPC client. (You can't create
# two users with the same username, and XML-RPC would otherwise try to
# create the same users that JSON-RPC created.)
foreach my $rpc ($jsonrpc, $xmlrpc) {
my @tests = (
# Permissions checks
{ args => { email => new_login(), full_name => NEW_FULLNAME,
password => NEW_PASSWORD },
error => "you are not authorized",
test => 'Logged-out user cannot call User.create',
},
{ user => 'unprivileged',
args => { email => new_login(), full_name => NEW_FULLNAME,
password => NEW_PASSWORD },
error => "you are not authorized",
test => 'Unprivileged user cannot call User.create',
},
# Login name checks.
{ user => 'admin',
args => { full_name => NEW_FULLNAME, password => NEW_PASSWORD },
error => "argument was not set",
test => 'Leaving out email argument fails',
},
{ user => 'admin',
args => { email => '', full_name => NEW_FULLNAME,
password => NEW_PASSWORD },
error => "argument was not set",
test => "Passing an empty email argument fails",
},
{ user => 'admin',
args => { email => INVALID_EMAIL, full_name => NEW_FULLNAME,
password => NEW_PASSWORD },
error => "didn't pass our syntax checking",
test => 'Invalid email address fails',
},
{ user => 'admin',
args => { email => new_login(128), full_name => NEW_FULLNAME,
password => NEW_PASSWORD },
error => "didn't pass our syntax checking",
test => 'Too long (> 127 chars) email address fails',
},
{ user => 'admin',
args => { email => $config->{unprivileged_user_login},
full_name => NEW_FULLNAME, password => NEW_PASSWORD },
error => "There is already an account",
test => 'Trying to use an existing login name fails',
},
{ user => 'admin',
args => { email => new_login(), full_name => NEW_FULLNAME,
password => PASSWORD_TOO_SHORT },
error => 'password must be at least',
test => 'Password Too Short fails',
},
{ user => 'admin',
args => { email => new_login(), full_name => NEW_FULLNAME,
password => NEW_PASSWORD },
test => 'Creating a user with all arguments and correct privileges',
},
{ user => 'admin',
args => { email => new_login(), password => NEW_PASSWORD },
test => 'Leaving out fullname works',
},
{ user => 'admin',
args => { email => new_login(), full_name => NEW_FULLNAME },
test => 'Leaving out password works',
},
);
$rpc->bz_run_tests(tests => \@tests, method => 'User.create',
post_success => \&post_success);
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
######################################
# Test for xmlrpc call to User.get() #
######################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use QA::Util;
use QA::Tests qw(PRIVATE_BUG_USER);
use Test::More tests => 330;
our ($config, @clients) = get_rpc_clients();
my $get_user = $config->{'unprivileged_user_login'};
my $canconfirm_user = $config->{'canconfirm_user_login'};
my $priv_user = $config->{PRIVATE_BUG_USER . '_user_login'};
my $disabled = $config->{'disabled_user_login'};
my $disabled_match = substr($disabled, 0, length($disabled) - 1);
# These are the basic tests. There are tests for include_fields
# and exclude_field below.
my @tests = (
{ args => { names => [$get_user] },
test => "Logged-out user can get unprivileged user by name"
},
{ args => { match => [$get_user] },
test => 'Logged-out user cannot use the match argument',
error => 'Logged-out users cannot use',
},
{ args => { ids => [1] },
test => 'Logged-out users cannot use the "ids" argument',
error => 'Logged-out users cannot use',
},
# match & names
{ user => 'unprivileged',
args => { names => [$get_user] },
test => "Unprivileged user can get himself",
},
{ user => 'unprivileged',
args => { match => [$get_user] },
test => 'Logged-in user can use the match argument',
},
{ user => 'unprivileged',
args => { match => [$get_user], names => [$get_user] },
test => 'Specifying the same thing in "match" and "names"',
},
# include_disabled
{ user => 'unprivileged',
args => { match => [$get_user, $disabled_match] },
test => 'Disabled users are not normally returned'
},
{ user => 'unprivileged',
args => { match => [$disabled_match], include_disabled => 1 },
test => 'Specifying include_disabled returns disabled users'
},
{ user => 'unprivileged',
args => { match => [$disabled] },
test => 'Full match on a disabled user returns that user',
},
# groups and group_ids
{ args => { groups => ['QA-Selenium-TEST'] },
test => 'Specifying just groups fails',
error => 'one of the following parameters',
},
{ args => { group_ids => [1] },
test => 'Specifying just group ids fails',
error => 'one of the following parameters',
},
{ args => { names => [$get_user, $priv_user], groups => ['QA-Selenium-TEST'] },
test => 'Limiting the return value to a group while being logged out fails',
error => 'The group you specified, QA-Selenium-TEST, is not valid here',
},
{ user => 'unprivileged',
args => { names => [$get_user, $priv_user], groups => ['missing_group'] },
test => 'Limiting the return value to a group which does not exist fails',
error => 'The group you specified, missing_group, is not valid here',
},
{ user => 'unprivileged',
args => { names => [$get_user, $priv_user], groups => ['QA-Selenium-TEST'] },
test => 'Limiting the return value to a group you do not belong to fails',
error => 'The group you specified, QA-Selenium-TEST, is not valid here',
},
{ user => 'editbugs',
args => { names => [$get_user, $priv_user], groups => ['canconfirm', 'editbugs'] },
test => 'Limiting the return value to some groups you do not belong to fails',
error => 'The group you specified, canconfirm, is not valid here',
},
{ user => 'admin',
args => { names => [$canconfirm_user], groups => ['canconfirm', 'editbugs'] },
test => 'Limiting the return value to groups you belong to',
},
# groups returned
{ user => 'admin',
args => { names => [$get_user] },
test => 'Admin can get user',
},
{ user => 'admin',
args => { names => [$canconfirm_user] },
test => 'Admin can get user',
},
{ user => 'canconfirm',
args => { names => [$canconfirm_user] },
test => 'Privileged user can get himself',
},
{ user => 'editbugs',
args => { names => [$canconfirm_user] },
test => 'Privileged user can get another user',
},
);
sub post_success {
my ($call, $t) = @_;
my $result = $call->result;
is(scalar @{ $result->{users} }, 1, "Got exactly one user");
my $item = $result->{users}->[0];
my $user = $t->{user} || '';
if ($user eq 'admin') {
ok(exists $item->{email} && exists $item->{can_login}
&& exists $item->{email_enabled} && exists $item->{login_denied_text},
'Admin correctly gets all user fields');
}
elsif ($user) {
ok(exists $item->{email} && exists $item->{can_login},
'Logged-in user correctly gets email and can_login');
ok(!exists $item->{email_enabled}
&& !exists $item->{login_denied_text},
"Non-admin user doesn't get email_enabled and login_denied_text");
}
else {
my @item_keys = sort keys %$item;
is_deeply(\@item_keys, ['id', 'name', 'real_name'],
'Only id, name, and real_name are returned to logged-out users');
return;
}
my $username = $config->{"${user}_user_login"};
# FIXME We have no way to create a saved search or a saved report from
# the WebService, so we cannot test that the correct data is returned
# if the user is accessing his own account.
if ($username eq $item->{name}) {
ok(exists $item->{saved_searches} && exists $item->{saved_reports},
'Users can get the list of saved searches and reports for their own account');
}
else {
ok(!exists $item->{saved_searches} && !exists $item->{saved_reports},
"Users cannot get the list of saved searches and reports from someone else's acccount");
}
my @groups = map { $_->{name} } @{$item->{groups}};
# Admins can see all groups a user belongs to (assuming they inherited
# membership for all groups). Same for a user querying his own account.
if ($username eq $item->{name} || $user eq 'admin') {
if ($username eq $get_user) {
ok(!scalar @groups, "The unprivileged user doesn't belong to any group");
}
elsif ($username eq $canconfirm_user) {
ok(grep($_ eq 'canconfirm', @groups), "Group 'canconfirm' returned");
}
}
else {
ok(!scalar @groups, "No groups are visible to users without bless privs");
}
}
foreach my $rpc (@clients) {
$rpc->bz_run_tests(tests => \@tests, method => 'User.get',
post_success => \&post_success);
#############################
# Include and Exclude Tests #
#############################
my $include_nothing = $rpc->bz_call_success('User.get', {
names => [$get_user], include_fields => ['asdfasdfsdf'],
}, 'User.get including only invalid fields');
is(scalar keys %{ $include_nothing->result->{users}->[0] }, 0,
'No fields returned for user');
my $include_one = $rpc->bz_call_success('User.get', {
names => [$get_user], include_fields => ['id'],
}, 'User.get including only id');
is(scalar keys %{ $include_one->result->{users}->[0] }, 1,
'Only one field returned for user');
my $exclude_none = $rpc->bz_call_success('User.get', {
names => [$get_user], exclude_fields => ['asdfasdfsdf'],
}, 'User.get excluding only invalid fields');
is(scalar keys %{ $exclude_none->result->{users}->[0] }, 3,
'All fields returned for user');
my $exclude_one = $rpc->bz_call_success('User.get', {
names => [$get_user], exclude_fields => ['id'],
}, 'User.get excluding id');
is(scalar keys %{ $exclude_one->result->{users}->[0] }, 2,
'Only two fields returned for user');
my $override = $rpc->bz_call_success('User.get', {
names => [$get_user], include_fields => ['id', 'name'],
exclude_fields => ['id']
}, 'User.get with both include and exclude');
is(scalar keys %{ $override->result->{users}->[0] }, 1,
'Only one field returned');
ok(exists $override->result->{users}->[0]->{name},
'...and that field is the "name" field');
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
##########################################################
# Test for xmlrpc call to User.login() and User.logout() #
##########################################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use Data::Dumper;
use QA::Util;
use Test::More tests => 119;
my ($config, @clients) = get_rpc_clients();
use constant INVALID_EMAIL => '@invalid_user@';
my $user = $config->{unprivileged_user_login};
my $pass = $config->{unprivileged_user_passwd};
my $error = "The login or password you entered is not valid";
my @tests = (
{ user => 'unprivileged',
test => "Unprivileged user can log in successfully",
},
{ args => { login => $user, password => '' },
error => $error,
test => "Empty password can't log in",
},
{ args => { login => '', password => $pass },
error => $error,
test => "Empty login can't log in",
},
{ args => { login => $user },
error => "requires a password argument",
test => "Undef password can't log in",
},
{ args => { password => $pass },
error => "requires a login argument",
test => "Undef login can't log in",
},
{ args => { login => INVALID_EMAIL, password => $pass },
error => $error,
test => "Invalid email can't log in",
},
{ args => { login => $user, password => '*' },
error => $error,
test => "Invalid password can't log in",
},
{ args => { login => $config->{disabled_user_login},
password => $config->{disabled_user_passwd} },
error => "!!This is the text!!",
test => "Can't log in with a disabled account",
},
{ args => { login => $config->{disabled_user_login}, password => '*' },
error => $error,
test => "Logging in with invalid password doesn't show disabledtext",
},
);
sub _login_args {
my $args = shift;
my %fixed_args = %$args;
$fixed_args{Bugzilla_login} = delete $fixed_args{login};
$fixed_args{Bugzilla_password} = delete $fixed_args{password};
return \%fixed_args;
}
foreach my $rpc (@clients) {
if ($rpc->bz_get_mode) {
$rpc->bz_call_fail('User.logout', undef, 'must use HTTP POST',
'User.logout fails when called via GET');
}
foreach my $t (@tests) {
if ($t->{user}) {
my $username = $config->{$t->{user} . '_user_login'};
my $password = $config->{$t->{user} . '_user_passwd'};
if ($rpc->bz_get_mode) {
$rpc->bz_call_fail('User.login',
{ login => $username, password => $password },
'must use HTTP POST', $t->{test} . ' (fails on GET)');
}
else {
$rpc->bz_log_in($t->{user});
ok($rpc->{_bz_credentials}->{token}, 'Login token returned');
$rpc->bz_call_success('User.logout');
}
if ($t->{error}) {
$rpc->bz_call_fail('Bugzilla.version',
{ Bugzilla_login => $username,
Bugzilla_password => $password });
}
else {
$rpc->bz_call_success('Bugzilla.version',
{ Bugzilla_login => $username,
Bugzilla_password => $password });
}
}
else {
# Under GET, there's no reason to have extra failing tests.
if (!$rpc->bz_get_mode) {
$rpc->bz_call_fail('User.login', $t->{args}, $t->{error},
$t->{test});
}
if (defined $t->{args}->{login}
and defined $t->{args}->{password})
{
my $fixed_args = _login_args($t->{args});
$rpc->bz_call_fail('Bugzilla.version', $fixed_args,
$t->{error}, "Bugzilla_login: " . $t->{test});
}
}
}
}
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
#########################################################
# Test for xmlrpc call to User.offer_account_by_email() #
#########################################################
use 5.10.1;
use strict;
use warnings;
use FindBin qw($RealBin);
use lib "$RealBin/../lib";
use QA::Util;
use Test::More tests => 29;
my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients();
# These are the characters that are actually invalid per RFC.
use constant INVALID_EMAIL => '()[]\;:,<>@webservice.test';
sub new_login {
return 'requested_' . random_string() . '@webservice.test';
}
$jsonrpc_get->bz_call_fail('User.offer_account_by_email',
{ email => new_login() },
'must use HTTP POST', 'offer_account_by_email fails over GET');
# Have to wrap @tests in the foreach so that new_login returns something
# different each time.
foreach my $rpc ($jsonrpc, $xmlrpc) {
my @tests = (
# Login name checks.
{ args => { },
error => "argument was not set",
test => 'Leaving out email argument fails',
},
{ args => { email => '' },
error => "argument was not set",
test => "Passing an empty email argument fails",
},
{ args => { email => INVALID_EMAIL },
error => "didn't pass our syntax checking",
test => 'Invalid email address fails',
},
{ args => { email => $config->{unprivileged_user_login} },
error => "There is already an account",
test => 'Trying to use an existing login name fails',
},
{ args => { email => new_login() },
test => 'Valid, non-existing email passes.',
},
);
$rpc->bz_run_tests(tests => \@tests,
method => 'User.offer_account_by_email');
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment