123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- import unittest
- import pytest
- from sentry.api.event_search import (
- AggregateFilter,
- AggregateKey,
- SearchFilter,
- SearchKey,
- SearchValue,
- )
- from sentry.api.issue_search import (
- convert_actor_or_none_value,
- convert_category_value,
- convert_first_release_value,
- convert_query_values,
- convert_release_value,
- convert_type_value,
- convert_user_value,
- parse_search_query,
- value_converters,
- )
- from sentry.exceptions import InvalidSearchQuery
- from sentry.models.group import STATUS_QUERY_CHOICES
- from sentry.testutils import TestCase
- from sentry.testutils.silo import region_silo_test
- from sentry.types.issues import GROUP_CATEGORY_TO_TYPES, GroupCategory
- class ParseSearchQueryTest(unittest.TestCase):
- def test_key_mappings(self):
- # Test a couple of keys to ensure things are working as expected
- assert parse_search_query("bookmarks:123") == [
- SearchFilter(
- key=SearchKey(name="bookmarked_by"), operator="=", value=SearchValue("123")
- )
- ]
- assert parse_search_query("first-release:123") == [
- SearchFilter(
- key=SearchKey(name="first_release"), operator="=", value=SearchValue("123")
- )
- ]
- assert parse_search_query("first-release:123 non_mapped:456") == [
- SearchFilter(
- key=SearchKey(name="first_release"), operator="=", value=SearchValue("123")
- ),
- SearchFilter(key=SearchKey(name="non_mapped"), operator="=", value=SearchValue("456")),
- ]
- def test_is_query_unassigned(self):
- assert parse_search_query("is:unassigned") == [
- SearchFilter(key=SearchKey(name="unassigned"), operator="=", value=SearchValue(True))
- ]
- assert parse_search_query("is:assigned") == [
- SearchFilter(key=SearchKey(name="unassigned"), operator="=", value=SearchValue(False))
- ]
- assert parse_search_query("!is:unassigned") == [
- SearchFilter(key=SearchKey(name="unassigned"), operator="!=", value=SearchValue(True))
- ]
- assert parse_search_query("!is:assigned") == [
- SearchFilter(key=SearchKey(name="unassigned"), operator="!=", value=SearchValue(False))
- ]
- def test_is_query_linked(self):
- assert parse_search_query("is:linked") == [
- SearchFilter(key=SearchKey(name="linked"), operator="=", value=SearchValue(True))
- ]
- assert parse_search_query("is:unlinked") == [
- SearchFilter(key=SearchKey(name="linked"), operator="=", value=SearchValue(False))
- ]
- assert parse_search_query("!is:linked") == [
- SearchFilter(key=SearchKey(name="linked"), operator="!=", value=SearchValue(True))
- ]
- assert parse_search_query("!is:unlinked") == [
- SearchFilter(key=SearchKey(name="linked"), operator="!=", value=SearchValue(False))
- ]
- def test_is_query_status(self):
- for status_string, status_val in STATUS_QUERY_CHOICES.items():
- assert parse_search_query("is:%s" % status_string) == [
- SearchFilter(
- key=SearchKey(name="status"), operator="=", value=SearchValue(status_val)
- )
- ]
- assert parse_search_query("!is:%s" % status_string) == [
- SearchFilter(
- key=SearchKey(name="status"), operator="!=", value=SearchValue(status_val)
- )
- ]
- def test_is_query_invalid(self):
- with pytest.raises(InvalidSearchQuery) as excinfo:
- parse_search_query("is:wrong")
- assert str(excinfo.value).startswith('Invalid value for "is" search, valid values are')
- def test_is_query_inbox(self):
- assert parse_search_query("is:for_review") == [
- SearchFilter(key=SearchKey(name="for_review"), operator="=", value=SearchValue(True))
- ]
- def test_numeric_filter(self):
- # test numeric format
- assert parse_search_query("times_seen:500") == [
- SearchFilter(
- key=SearchKey(name="times_seen"), operator="=", value=SearchValue(raw_value=500)
- )
- ]
- assert parse_search_query("times_seen:>500") == [
- SearchFilter(
- key=SearchKey(name="times_seen"), operator=">", value=SearchValue(raw_value=500)
- )
- ]
- assert parse_search_query("times_seen:<500") == [
- SearchFilter(
- key=SearchKey(name="times_seen"), operator="<", value=SearchValue(raw_value=500)
- )
- ]
- invalid_queries = [
- "times_seen:<hello",
- "times_seen:<512.1.0",
- "times_seen:2018-01-01",
- "times_seen:+7d",
- "times_seen:>2018-01-01",
- 'times_seen:"<10"',
- ]
- for invalid_query in invalid_queries:
- with pytest.raises(InvalidSearchQuery, match="Invalid number"):
- parse_search_query(invalid_query)
- def test_boolean_operators_not_allowed(self):
- invalid_queries = [
- "user.email:foo@example.com OR user.email:bar@example.com",
- "user.email:foo@example.com AND user.email:bar@example.com",
- "user.email:foo@example.com OR user.email:bar@example.com OR user.email:foobar@example.com",
- "user.email:foo@example.com AND user.email:bar@example.com AND user.email:foobar@example.com",
- ]
- for invalid_query in invalid_queries:
- with pytest.raises(
- InvalidSearchQuery,
- match='Boolean statements containing "OR" or "AND" are not supported in this search',
- ):
- parse_search_query(invalid_query)
- def test_parens_in_query(self):
- assert parse_search_query(
- "TypeError Anonymous function(app/javascript/utils/transform-object-keys)"
- ) == [
- SearchFilter(
- key=SearchKey(name="message"),
- operator="=",
- value=SearchValue(
- raw_value="TypeError Anonymous function(app/javascript/utils/transform-object-keys)"
- ),
- ),
- ]
- @region_silo_test(stable=True)
- class ConvertJavaScriptConsoleTagTest(TestCase):
- def test_valid(self):
- filters = [SearchFilter(SearchKey("empty_stacktrace.js_console"), "=", SearchValue(True))]
- with self.feature("organizations:javascript-console-error-tag"):
- result = convert_query_values(filters, [self.project], self.user, None)
- assert result[0].value.raw_value is True
- def test_invalid(self):
- filters = [SearchFilter(SearchKey("empty_stacktrace.js_console"), "=", SearchValue(True))]
- with self.feature({"organizations:javascript-console-error-tag": False}) and pytest.raises(
- InvalidSearchQuery,
- match="The empty_stacktrace.js_console filter is not supported for this organization",
- ):
- convert_query_values(filters, [self.project], self.user, None)
- @region_silo_test(stable=True)
- class ConvertQueryValuesTest(TestCase):
- def test_valid_converter(self):
- filters = [SearchFilter(SearchKey("assigned_to"), "=", SearchValue("me"))]
- expected = value_converters["assigned_to"](
- [filters[0].value.raw_value], [self.project], self.user, None
- )
- filters = convert_query_values(filters, [self.project], self.user, None)
- assert filters[0].value.raw_value == expected
- def test_no_converter(self):
- search_val = SearchValue("me")
- filters = [SearchFilter(SearchKey("something"), "=", search_val)]
- filters = convert_query_values(filters, [self.project], self.user, None)
- assert filters[0].value.raw_value == search_val.raw_value
- @region_silo_test(stable=True)
- class ConvertStatusValueTest(TestCase):
- def test_valid(self):
- for status_string, status_val in STATUS_QUERY_CHOICES.items():
- filters = [SearchFilter(SearchKey("status"), "=", SearchValue([status_string]))]
- result = convert_query_values(filters, [self.project], self.user, None)
- assert result[0].value.raw_value == [status_val]
- filters = [SearchFilter(SearchKey("status"), "=", SearchValue([status_val]))]
- result = convert_query_values(filters, [self.project], self.user, None)
- assert result[0].value.raw_value == [status_val]
- def test_invalid(self):
- filters = [SearchFilter(SearchKey("status"), "=", SearchValue("wrong"))]
- with pytest.raises(InvalidSearchQuery, match="invalid status value"):
- convert_query_values(filters, [self.project], self.user, None)
- filters = [AggregateFilter(AggregateKey("count_unique(user)"), ">", SearchValue("1"))]
- with pytest.raises(
- InvalidSearchQuery,
- match=r"Aggregate filters \(count_unique\(user\)\) are not supported in issue searches.",
- ):
- convert_query_values(filters, [self.project], self.user, None)
- @region_silo_test(stable=True)
- class ConvertActorOrNoneValueTest(TestCase):
- def test_user(self):
- assert convert_actor_or_none_value(
- ["me"], [self.project], self.user, None
- ) == convert_user_value(["me"], [self.project], self.user, None)
- def test_none(self):
- assert convert_actor_or_none_value(["none"], [self.project], self.user, None) == [None]
- def test_team(self):
- assert convert_actor_or_none_value(
- [f"#{self.team.slug}"], [self.project], self.user, None
- ) == [self.team]
- def test_invalid_team(self):
- assert (
- convert_actor_or_none_value(["#never_upgrade"], [self.project], self.user, None)[0].id
- == 0
- )
- @region_silo_test
- class ConvertUserValueTest(TestCase):
- def test_me(self):
- assert convert_user_value(["me"], [self.project], self.user, None) == [self.user]
- def test_specified_user(self):
- user = self.create_user()
- assert convert_user_value([user.username], [self.project], self.user, None) == [user]
- def test_invalid_user(self):
- assert convert_user_value(["fake-user"], [], None, None)[0].id == 0
- @region_silo_test(stable=True)
- class ConvertReleaseValueTest(TestCase):
- def test(self):
- assert convert_release_value(["123"], [self.project], self.user, None) == "123"
- def test_latest(self):
- release = self.create_release(self.project)
- assert convert_release_value(["latest"], [self.project], self.user, None) == release.version
- assert convert_release_value(["14.*"], [self.project], self.user, None) == "14.*"
- @region_silo_test(stable=True)
- class ConvertFirstReleaseValueTest(TestCase):
- def test(self):
- assert convert_first_release_value(["123"], [self.project], self.user, None) == ["123"]
- def test_latest(self):
- release = self.create_release(self.project)
- assert convert_first_release_value(["latest"], [self.project], self.user, None) == [
- release.version
- ]
- assert convert_first_release_value(["14.*"], [self.project], self.user, None) == ["14.*"]
- @region_silo_test(stable=True)
- class ConvertCategoryValueTest(TestCase):
- def test(self):
- with self.feature("organizations:performance-issues"):
- assert set(convert_category_value(["error"], [self.project], self.user, None)) == {
- gt.value for gt in GROUP_CATEGORY_TO_TYPES[GroupCategory.ERROR]
- }
- assert set(
- convert_category_value(["performance"], [self.project], self.user, None)
- ) == {gt.value for gt in GROUP_CATEGORY_TO_TYPES[GroupCategory.PERFORMANCE]}
- assert set(
- convert_category_value(["error", "performance"], [self.project], self.user, None)
- ) == {
- gt.value
- for gt in GROUP_CATEGORY_TO_TYPES[GroupCategory.ERROR]
- + GROUP_CATEGORY_TO_TYPES[GroupCategory.PERFORMANCE]
- }
- with pytest.raises(InvalidSearchQuery):
- convert_category_value(["hellboy"], [self.project], self.user, None)
- @region_silo_test(stable=True)
- class ConvertTypeValueTest(TestCase):
- def test(self):
- with self.feature("organizations:performance-issues"):
- assert convert_type_value(["error"], [self.project], self.user, None) == [1]
- assert convert_type_value(
- ["performance_n_plus_one_db_queries"], [self.project], self.user, None
- ) == [1006]
- assert convert_type_value(
- ["performance_slow_span"], [self.project], self.user, None
- ) == [1001]
- assert convert_type_value(
- ["error", "performance_n_plus_one_db_queries"], [self.project], self.user, None
- ) == [1, 1006]
- with pytest.raises(InvalidSearchQuery):
- convert_type_value(["hellboy"], [self.project], self.user, None)
|