blob: 5d84ec6ff775c6e07df94ebd2950dd74a2c649de [file] [log] [blame]
# 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;