def test_key_remapping(self): class RemapVisitor(SearchVisitor): key_mappings = { 'target_value': ['someValue', 'legacy-value'], } tree = event_search_grammar.parse( 'someValue:123 legacy-value:456 normal_value:hello') assert RemapVisitor().visit(tree) == [ SearchFilter( key=SearchKey(name='target_value'), operator='=', value=SearchValue('123'), ), SearchFilter( key=SearchKey(name='target_value'), operator='=', value=SearchValue('456'), ), SearchFilter( key=SearchKey(name='normal_value'), operator='=', value=SearchValue('hello'), ), ]
def test_quoted_raw_search_anywhere(self): assert parse_search_query('"hello there" user.email:[email protected] "general kenobi"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello there'), ), SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='general kenobi'), ), ] assert parse_search_query(' " hello " ') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value=' hello '), ), ] assert parse_search_query(' " he\\"llo " ') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value=' he"llo '), ), ]
def test_allowed_keys(self): config = SearchConfig(allowed_keys=["good_key"]) assert parse_search_query("good_key:123 bad_key:123 text") == [ SearchFilter(key=SearchKey(name="good_key"), operator="=", value=SearchValue("123")), SearchFilter(key=SearchKey(name="bad_key"), operator="=", value=SearchValue("123")), SearchFilter(key=SearchKey(name="message"), operator="=", value=SearchValue("text")), ] with pytest.raises(InvalidSearchQuery, match="Invalid key for this search"): assert parse_search_query("good_key:123 bad_key:123 text", config=config) assert parse_search_query("good_key:123 text", config=config) == [ SearchFilter(key=SearchKey(name="good_key"), operator="=", value=SearchValue("123")), SearchFilter(key=SearchKey(name="message"), operator="=", value=SearchValue("text")), ]
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 self.assertRaises(InvalidSearchQuery, expected_regex="Invalid number"): parse_search_query(invalid_query)
def test_release(self): assert parse_search_query(f"{RELEASE_ALIAS}:12") == [ SearchFilter(key=SearchKey(name=RELEASE_ALIAS), operator="=", value=SearchValue("12")) ] assert parse_search_query(f"{RELEASE_ALIAS}:12*") == [ SearchFilter(key=SearchKey(name=RELEASE_ALIAS), operator="=", value=SearchValue("12*")), ]
def test_release_stage(self): assert parse_search_query(f"{RELEASE_STAGE_ALIAS}:adopted") == [ SearchFilter( key=SearchKey(name=RELEASE_STAGE_ALIAS), operator="=", value=SearchValue("adopted") ) ] assert parse_search_query(f"!{RELEASE_STAGE_ALIAS}:replaced") == [ SearchFilter( key=SearchKey(name=RELEASE_STAGE_ALIAS), operator="!=", value=SearchValue("replaced"), ) ] assert parse_search_query(f"{RELEASE_STAGE_ALIAS}:[adopted, not_adopted]") == [ SearchFilter( key=SearchKey(name=RELEASE_STAGE_ALIAS), operator="IN", value=SearchValue(["adopted", "not_adopted"]), ), ] assert parse_search_query(f"!{RELEASE_STAGE_ALIAS}:[replaced, adopted]") == [ SearchFilter( key=SearchKey(name=RELEASE_STAGE_ALIAS), operator="NOT IN", value=SearchValue(["replaced", "adopted"]), ), ]
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 setUp(self): super(ParseBooleanSearchQueryTest, self).setUp() self.term1 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ) self.term2 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ) self.term3 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ) self.term4 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ) self.term5 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), )
def test_boolean_filter(self): truthy = ("true", "TRUE", "1") for val in truthy: assert parse_search_query(f"stack.in_app:{val}") == [ SearchFilter( key=SearchKey(name="stack.in_app"), operator="=", value=SearchValue(raw_value=1), ) ] falsey = ("false", "FALSE", "0") for val in falsey: assert parse_search_query(f"stack.in_app:{val}") == [ SearchFilter( key=SearchKey(name="stack.in_app"), operator="=", value=SearchValue(raw_value=0), ) ] assert parse_search_query("!stack.in_app:false") == [ SearchFilter( key=SearchKey(name="stack.in_app"), operator="=", value=SearchValue(raw_value=1), ) ]
def test_numeric_measurements_filter(self): # NOTE: can only filter on integers right now assert parse_search_query("measurements.size:3.1415") == [ SearchFilter( key=SearchKey(name="measurements.size"), operator="=", value=SearchValue(raw_value=3.1415), ) ] assert parse_search_query("measurements.size:>3.1415") == [ SearchFilter( key=SearchKey(name="measurements.size"), operator=">", value=SearchValue(raw_value=3.1415), ) ] assert parse_search_query("measurements.size:<3.1415") == [ SearchFilter( key=SearchKey(name="measurements.size"), operator="<", value=SearchValue(raw_value=3.1415), ) ]
def test_escaped_quote_value(self): assert parse_search_query('device.family:\\"') == [ SearchFilter(key=SearchKey(name="device.family"), operator="=", value=SearchValue(raw_value='"')) ] assert parse_search_query('device.family:te\\"st') == [ SearchFilter( key=SearchKey(name="device.family"), operator="=", value=SearchValue(raw_value='te"st'), ) ] # This is a weird case. I think this should be an error, but it doesn't seem trivial to rewrite # the grammar to handle that. assert parse_search_query('url:"te"st') == [ SearchFilter(key=SearchKey(name="url"), operator="=", value=SearchValue(raw_value="te")), SearchFilter(key=SearchKey(name="message"), operator="=", value=SearchValue(raw_value="st")), ]
def test_other_dates(self): # test date format with other name assert parse_search_query("first_seen:>2015-05-18") == [ SearchFilter( key=SearchKey(name="first_seen"), operator=">", value=SearchValue(raw_value=datetime.datetime( 2015, 5, 18, 0, 0, tzinfo=timezone.utc)), ) ] assert parse_search_query("first_seen:>2018-01-01T05:06:07+00:00") == [ SearchFilter( key=SearchKey(name="first_seen"), operator=">", value=SearchValue(raw_value=datetime.datetime( 2018, 1, 1, 5, 6, 7, tzinfo=timezone.utc)), ) ] assert parse_search_query("random:>2015-05-18") == [ SearchFilter(key=SearchKey(name="random"), operator="=", value=SearchValue(">2015-05-18")) ]
def test_quoted_free_text_search_anywhere(self): assert parse_search_query( '"hello there" user.email:[email protected] "general kenobi"') == [ SearchFilter( key=SearchKey(name="message"), operator="=", value=SearchValue(raw_value="hello there"), ), SearchFilter( key=SearchKey(name="user.email"), operator="=", value=SearchValue(raw_value="*****@*****.**"), ), SearchFilter( key=SearchKey(name="message"), operator="=", value=SearchValue(raw_value="general kenobi"), ), ] assert parse_search_query(' " hello " ') == [ SearchFilter(key=SearchKey(name="message"), operator="=", value=SearchValue(raw_value=" hello ")) ] assert parse_search_query(' " he\\"llo " ') == [ SearchFilter(key=SearchKey(name="message"), operator="=", value=SearchValue(raw_value=' he"llo ')) ]
def test_duration_aggregate_measurements_filter(self): assert parse_search_query("percentile(measurements.fp, 0.5):3.3s") == [ SearchFilter( key=SearchKey(name="percentile(measurements.fp, 0.5)"), operator="=", value=SearchValue(raw_value=3300), ) ] assert parse_search_query( "percentile(measurements.fp, 0.5):>3.3s") == [ SearchFilter( key=SearchKey(name="percentile(measurements.fp, 0.5)"), operator=">", value=SearchValue(raw_value=3300), ) ] assert parse_search_query( "percentile(measurements.fp, 0.5):<3.3s") == [ SearchFilter( key=SearchKey(name="percentile(measurements.fp, 0.5)"), operator="<", value=SearchValue(raw_value=3300), ) ]
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_multiple_quotes(self): assert parse_search_query('device.family:"" browser.name:"Chrome"') == [ SearchFilter( key=SearchKey(name='device.family'), operator='=', value=SearchValue(raw_value=''), ), SearchFilter( key=SearchKey(name='browser.name'), operator='=', value=SearchValue(raw_value='Chrome'), ), ] assert parse_search_query('device.family:"\\"" browser.name:"Chrome"') == [ SearchFilter( key=SearchKey(name='device.family'), operator='=', value=SearchValue(raw_value='"'), ), SearchFilter( key=SearchKey(name='browser.name'), operator='=', value=SearchValue(raw_value='Chrome'), ), ]
def test_rel_time_filter(self): now = timezone.now() with freeze_time(now): assert parse_search_query('first_seen:+7d') == [ SearchFilter( key=SearchKey(name='first_seen'), operator="<=", value=SearchValue( raw_value=now - timedelta(days=7), ), ), ] assert parse_search_query('first_seen:-2w') == [ SearchFilter( key=SearchKey(name='first_seen'), operator=">=", value=SearchValue( raw_value=now - timedelta(days=14), ), ), ] assert parse_search_query('random:-2w') == [ SearchFilter( key=SearchKey(name='random'), operator="=", value=SearchValue('-2w'), ), ]
def test_parse_search_query(self): # test with raw search query at the end assert parse_search_query('user.email:[email protected] release:1.2.1 hello') == [ SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ), SearchFilter( key=SearchKey(name='release'), operator="=", value=SearchValue(raw_value='1.2.1'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ) ] # if the search query starts with the raw query, assume the whole thing is a raw string assert parse_search_query('hello user.email:[email protected] release:1.2.1') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello user.email:[email protected] release:1.2.1'), ), ]
def is_filter_translators(self): is_filter_translators = { 'assigned': (SearchKey('unassigned'), SearchValue(False)), 'unassigned': (SearchKey('unassigned'), SearchValue(True)), } for status_key, status_value in STATUS_CHOICES.items(): is_filter_translators[status_key] = (SearchKey('status'), SearchValue(status_value)) return is_filter_translators
def is_filter_translators(self): is_filter_translators = { "assigned": (SearchKey("unassigned"), SearchValue(False)), "unassigned": (SearchKey("unassigned"), SearchValue(True)), } for status_key, status_value in STATUS_CHOICES.items(): is_filter_translators[status_key] = (SearchKey("status"), SearchValue(status_value)) return is_filter_translators
def test_custom_tag(self): assert parse_search_query("fruit:apple release:1.2.1") == [ SearchFilter( key=SearchKey(name="fruit"), operator="=", value=SearchValue(raw_value="apple") ), SearchFilter( key=SearchKey(name="release"), operator="=", value=SearchValue(raw_value="1.2.1") ), ]
def test_valid(self): for status_string, status_val in STATUS_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_not_has_tag(self): # unquoted key assert parse_search_query("!has:release") == [ SearchFilter(key=SearchKey(name="release"), operator="=", value=SearchValue("")) ] # quoted key assert parse_search_query('!has:"hi:there"') == [ SearchFilter(key=SearchKey(name="hi:there"), operator="=", value=SearchValue("")) ]
def test_escaped_quotes(self): assert parse_search_query('release:"a\\"thing\\""') == [ SearchFilter(key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a"thing"')), ] assert parse_search_query('release:"a\\"\\"release"') == [ SearchFilter(key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a""release')), ]
def test_quoted_key(self): assert parse_search_query('"hi:there":value') == [ SearchFilter( key=SearchKey(name="hi:there"), operator="=", value=SearchValue(raw_value="value") ) ] assert parse_search_query('!"hi:there":value') == [ SearchFilter( key=SearchKey(name="hi:there"), operator="!=", value=SearchValue(raw_value="value") ) ]
def test_invalid(self): filters = [SearchFilter(SearchKey("status"), "=", SearchValue("wrong"))] with self.assertRaises(InvalidSearchQuery, expected_regex="invalid status value"): convert_query_values(filters, [self.project], self.user, None) filters = [AggregateFilter(AggregateKey("count_unique(user)"), ">", SearchValue("1"))] with self.assertRaises( InvalidSearchQuery, expected_regex="Aggregate filters (count_unique(user)) are not supported in issue searches.", ): convert_query_values(filters, [self.project], self.user, None)
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_tab_outside_quote(self): # tab outside quote assert parse_search_query('release:a\trelease') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a'), ), SearchFilter(key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='\trelease')), ]
def test_negated_on_boolean_values_and_non_boolean_field(self): assert parse_search_query("!user.id:true") == [ SearchFilter( key=SearchKey(name="user.id"), operator="!=", value=SearchValue(raw_value="true") ) ] assert parse_search_query("!user.id:1") == [ SearchFilter( key=SearchKey(name="user.id"), operator="!=", value=SearchValue(raw_value="1") ) ]
def test_tab_outside_quote(self): # tab outside quote assert parse_search_query("release:a\trelease") == [ SearchFilter( key=SearchKey(name="release"), operator="=", value=SearchValue(raw_value="a") ), SearchFilter( key=SearchKey(name="message"), operator="=", value=SearchValue(raw_value="\trelease"), ), ]