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_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
def test_search_value(raw, result): search_value = SearchValue(raw) assert search_value.value == result
def node_visitor(token): if token["type"] == "spaces": return None if token["type"] == "filter": # Filters with an invalid reason raises to signal to the test # runner that we should expect this exception if token.get("invalid"): raise InvalidSearchQuery(token["invalid"]["reason"]) # Transform the operator to match for list values if token["value"]["type"] in ["valueTextList", "valueNumberList"]: operator = "NOT IN" if token["negated"] else "IN" else: # Negate the operator if the filter is negated to match operator = token["operator"] or "=" operator = f"!{operator}" if token["negated"] else operator key = node_visitor(token["key"]) value = node_visitor(token["value"]) if token["filter"] == "boolean" and token["negated"]: operator = "=" value = SearchValue(raw_value=1 if value.raw_value == 0 else 0) return SearchFilter(key, operator, value) if token["type"] == "keySimple": return SearchKey(name=token["value"]) if token["type"] == "keyExplicitTag": return SearchKey(name=f"tags[{token['key']['value']}]") if token["type"] == "keyAggregate": name = node_visitor(token["name"]).name # Consistent join aggregate function parameters args = ", ".join(arg["value"]["value"] for arg in token["args"]["args"]) return AggregateKey(name=f"{name}({args})") if token["type"] == "valueText": # Noramlize values by removing the escaped quotes value = token["value"].replace('\\"', '"') return SearchValue(raw_value=value) if token["type"] == "valueNumber": return SearchValue(raw_value=parse_numeric_value(token["value"], token["unit"])) if token["type"] == "valueTextList": return SearchValue(raw_value=[item["value"]["value"] for item in token["items"]]) if token["type"] == "valueNumberList": return SearchValue(raw_value=[item["value"]["rawValue"] for item in token["items"]]) if token["type"] == "valueIso8601Date": return SearchValue(raw_value=parse_datetime_string(token["value"])) if token["type"] == "valueDuration": return SearchValue(raw_value=parse_duration(token["value"], token["unit"])) if token["type"] == "valueRelativeDate": return SearchValue(raw_value=parse_duration(token["value"], token["unit"])) if token["type"] == "valueBoolean": return SearchValue(raw_value=int(token["value"])) if token["type"] == "freeText": if token["quoted"]: # Normalize quotes value = token["value"].replace('\\"', '"') else: # Normalize spacing value = token["value"].strip(" ") if value == "": return None return SearchFilter( key=SearchKey(name="message"), operator="=", value=SearchValue(raw_value=value), )
def test_raw_search_anywhere(self): assert parse_search_query( 'hello what user.email:[email protected] where release:1.2.1 when' ) == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello what'), ), SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='where'), ), SearchFilter( key=SearchKey(name='release'), operator="=", value=SearchValue(raw_value='1.2.1'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='when'), ), ] assert parse_search_query('hello') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ), ] assert parse_search_query(' hello ') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ), ] assert parse_search_query(' hello there') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello there'), ), ] assert parse_search_query(' hello there:bye') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='hello'), ), SearchFilter( key=SearchKey(name='there'), operator='=', value=SearchValue(raw_value='bye'), ), ]
def test_multiple_statements(self): assert parse_search_query( 'user.email:[email protected] OR user.email:[email protected] OR user.email:[email protected]' ) == [ SearchBoolean(left_term=self.term1, operator='OR', right_term=SearchBoolean(left_term=self.term2, operator='OR', right_term=self.term3)) ] assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=self.term1, operator='AND', right_term=SearchBoolean(left_term=self.term2, operator='AND', right_term=self.term3)) ] term4 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ) # longer even number of terms assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=term4)) ] term5 = SearchFilter( key=SearchKey(name='user.email'), operator="=", value=SearchValue(raw_value='*****@*****.**'), ) # longer odd number of terms assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean(left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=term4, operator='AND', right_term=term5))) ] # absurdly long assert parse_search_query( 'user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] OR user.email:[email protected] AND user.email:[email protected] AND user.email:[email protected]' ) == [ SearchBoolean( left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean( left_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=term4, operator='AND', right_term=term5)), operator='OR', right_term=SearchBoolean( left_term=SearchBoolean(left_term=self.term1, operator='AND', right_term=self.term2), operator='OR', right_term=SearchBoolean(left_term=self.term3, operator='AND', right_term=SearchBoolean( left_term=term4, operator='AND', right_term=term5))))) ]
def test_quotes_filtered_on_raw(self): # Enclose the full raw query? Strip it. assert parse_search_query('thinger:unknown "what is this?"') == [ SearchFilter( key=SearchKey(name='thinger'), operator='=', value=SearchValue(raw_value='unknown'), ), SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='what is this?'), ), ] # Enclose the full query? Strip it and the whole query is raw. assert parse_search_query('"thinger:unknown what is this?"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='thinger:unknown what is this?'), ), ] # Allow a single quotation at end assert parse_search_query('end"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='end"'), ), ] # Allow a single quotation at beginning assert parse_search_query('"beginning') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='"beginning'), ), ] # Allow a single quotation assert parse_search_query('"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='"'), ), ] # Empty quotations become a dropped term assert parse_search_query('""') == [] # Allow a search for space assert parse_search_query('" "') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value=' '), ), ] # Strip in a balanced manner assert parse_search_query('""woof"') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='woof"'), ), ] # Don't try this at home kids assert parse_search_query('"""""""""') == [ SearchFilter( key=SearchKey(name='message'), operator='=', value=SearchValue(raw_value='"'), ), ]
def _build_search_filter(self, key_name, operator, value): return SearchFilter( key=SearchKey(name=key_name), operator=operator, value=SearchValue(raw_value=value), )
def test_tab_within_quote(self): assert parse_search_query('release:"a\trelease"') == [ SearchFilter(key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a\trelease')), ]
def test_negated_duration_on_non_duration_field(self): assert parse_search_query("!user.id:500s") == [ SearchFilter(key=SearchKey(name="user.id"), operator="!=", value=SearchValue(raw_value="500s")) ]
def test_simple_in(self): assert parse_search_query( "user.email:[[email protected]] test:[hello]") == [ SearchFilter( key=SearchKey(name="user.email"), operator="IN", value=SearchValue(raw_value=["*****@*****.**"]), ), SearchFilter( key=SearchKey(name="test"), operator="IN", value=SearchValue(raw_value=["hello"]), ), ] assert parse_search_query( "user.email:[[email protected],[email protected],[email protected]] test:[hello]" ) == [ SearchFilter( key=SearchKey(name="user.email"), operator="IN", value=SearchValue(raw_value=[ "*****@*****.**", "*****@*****.**", "*****@*****.**" ]), ), SearchFilter( key=SearchKey(name="test"), operator="IN", value=SearchValue(raw_value=["hello"]), ), ] assert parse_search_query( "!user.email:[[email protected], [email protected], [email protected]] test:[hello]" ) == [ SearchFilter( key=SearchKey(name="user.email"), operator="NOT IN", value=SearchValue(raw_value=[ "*****@*****.**", "*****@*****.**", "*****@*****.**" ]), ), SearchFilter( key=SearchKey(name="test"), operator="IN", value=SearchValue(raw_value=["hello"]), ), ] # Make sure brackets still work in normal values assert parse_search_query("test:h[e]llo]") == [ SearchFilter( key=SearchKey(name="test"), operator="=", value=SearchValue(raw_value="h[e]llo]"), ), ] assert parse_search_query("test:[h[e]llo") == [ SearchFilter( key=SearchKey(name="test"), operator="=", value=SearchValue(raw_value="[h[e]llo"), ), ] assert parse_search_query('test:"[h]"') == [ SearchFilter( key=SearchKey(name="test"), operator="=", value=SearchValue(raw_value="[h]"), ), ] assert parse_search_query("test:[h]*") == [ SearchFilter( key=SearchKey(name="test"), operator="=", value=SearchValue(raw_value="[h]*"), ), ] assert parse_search_query("test:[h e]") == [ SearchFilter( key=SearchKey(name="test"), operator="=", value=SearchValue(raw_value="[h"), ), SearchFilter( key=SearchKey(name="message"), operator="=", value=SearchValue(raw_value="e]"), ), ] assert parse_search_query("test:[]") == [ SearchFilter( key=SearchKey(name="test"), operator="=", value=SearchValue(raw_value="[]"), ), ] assert parse_search_query('user.email:[[email protected], "hi", 1]') == [ SearchFilter( key=SearchKey(name="user.email"), operator="IN", value=SearchValue(raw_value=["*****@*****.**", "hi", "1"]), ) ] assert parse_search_query('user.email:[[email protected], "hi", 1.0]') == [ SearchFilter( key=SearchKey(name="user.email"), operator="IN", value=SearchValue(raw_value=["*****@*****.**", "hi", "1.0"]), ) ] assert parse_search_query("test:[[h]]") == [ SearchFilter( key=SearchKey(name="test"), operator="IN", value=SearchValue(raw_value=["[h]"]), ), ] assert parse_search_query("test:[a, [h]]") == [ SearchFilter( key=SearchKey(name="test"), operator="IN", value=SearchValue(raw_value=["a", "[h]"]), ), ] assert parse_search_query( "user.email:[[email protected]]user.email:[email protected]") == [ SearchFilter( key=SearchKey(name="user.email"), operator="=", value=SearchValue( raw_value="[[email protected]]user.email:[email protected]"), ), ]
def test_parse_search_query_weird_values(self): # quotes within quotes assert parse_search_query('release:"a"thing""') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a"thing"'), ), ] # newline within quote assert parse_search_query('release:"a\nrelease"') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a\nrelease') ), ] # newline outside quote with self.assertRaises(IncompleteParseError): parse_search_query('release:a\nrelease') # tab within quote assert parse_search_query('release:"a\trelease"') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a\trelease') ), ] # 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') ), ] # escaped quotes 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') ), ] # poorly escaped quotes assert parse_search_query('release:"a release\"') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a release') ), ] assert parse_search_query('release:\"a release "') == [ SearchFilter( key=SearchKey(name='release'), operator='=', value=SearchValue(raw_value='a release ') ), ]
def convert_search_filter(search_filter): if search_filter.key.name in value_converters: converter = value_converters[search_filter.key.name] new_value = converter(search_filter.value.raw_value, projects, user) search_filter = search_filter._replace(value=SearchValue(new_value)) return search_filter