narrow: Use NarrowParameter instead of dictionary.

Earlier we were using the type `OptionalNarrowListT` for all functions
that required "narrow" as a parameter.
This commit changes all the functions accepting a "narrow"
to use a list of the new `NarrowParameter`
instead of `OptionalNarrowListT` which is a list of dicts.
This commit is contained in:
Kenneth Rodrigues
2024-06-19 19:17:34 +05:30
committed by Tim Abbott
parent 4e05381897
commit 15accfc983
4 changed files with 231 additions and 215 deletions

View File

@@ -150,7 +150,7 @@ class NarrowParameter(BaseModel):
return self return self
def is_spectator_compatible(narrow: Iterable[Dict[str, Any]]) -> bool: def is_spectator_compatible(narrow: Iterable[NarrowParameter]) -> bool:
# This implementation should agree with is_spectator_compatible in hash_parser.ts. # This implementation should agree with is_spectator_compatible in hash_parser.ts.
supported_operators = [ supported_operators = [
*channel_operators, *channel_operators,
@@ -163,15 +163,13 @@ def is_spectator_compatible(narrow: Iterable[Dict[str, Any]]) -> bool:
"id", "id",
] ]
for element in narrow: for element in narrow:
operator = element["operator"] operator = element.operator
if "operand" not in element:
return False
if operator not in supported_operators: if operator not in supported_operators:
return False return False
return True return True
def is_web_public_narrow(narrow: Optional[Iterable[Dict[str, Any]]]) -> bool: def is_web_public_narrow(narrow: Optional[Iterable[NarrowParameter]]) -> bool:
if narrow is None: if narrow is None:
return False return False
@@ -179,9 +177,9 @@ def is_web_public_narrow(narrow: Optional[Iterable[Dict[str, Any]]]) -> bool:
# Web-public queries are only allowed for limited types of narrows. # Web-public queries are only allowed for limited types of narrows.
# term == {'operator': 'channels', 'operand': 'web-public', 'negated': False} # term == {'operator': 'channels', 'operand': 'web-public', 'negated': False}
# or term == {'operator': 'streams', 'operand': 'web-public', 'negated': False} # or term == {'operator': 'streams', 'operand': 'web-public', 'negated': False}
term["operator"] in channels_operators term.operator in channels_operators
and term["operand"] == "web-public" and term.operand == "web-public"
and term["negated"] is False and term.negated is False
for term in narrow for term in narrow
) )
@@ -204,8 +202,6 @@ class BadNarrowOperatorError(JsonableError):
ConditionTransform: TypeAlias = Callable[[ClauseElement], ClauseElement] ConditionTransform: TypeAlias = Callable[[ClauseElement], ClauseElement]
OptionalNarrowListT: TypeAlias = Optional[List[Dict[str, Any]]]
# These delimiters will not appear in rendered messages or HTML-escaped topics. # These delimiters will not appear in rendered messages or HTML-escaped topics.
TS_START = "<ts-match>" TS_START = "<ts-match>"
TS_STOP = "</ts-match>" TS_STOP = "</ts-match>"
@@ -307,7 +303,7 @@ class NarrowBuilder:
"No message can be both a channel message and direct message" "No message can be both a channel message and direct message"
) )
def add_term(self, query: Select, term: Dict[str, Any]) -> Select: def add_term(self, query: Select, term: NarrowParameter) -> Select:
""" """
Extend the given query to one narrowed by the given term, and return the result. Extend the given query to one narrowed by the given term, and return the result.
@@ -320,10 +316,10 @@ class NarrowBuilder:
# methods to the same criterion. See the class's block comment # methods to the same criterion. See the class's block comment
# for details. # for details.
operator = term["operator"] operator = term.operator
operand = term["operand"] operand = term.operand
negated = term.get("negated", False) negated = term.negated
if operator in self.by_method_map: if operator in self.by_method_map:
method = self.by_method_map[operator] method = self.by_method_map[operator]
@@ -804,7 +800,9 @@ class NarrowBuilder:
def ok_to_include_history( def ok_to_include_history(
narrow: OptionalNarrowListT, user_profile: Optional[UserProfile], is_web_public_query: bool narrow: Optional[List[NarrowParameter]],
user_profile: Optional[UserProfile],
is_web_public_query: bool,
) -> bool: ) -> bool:
# There are occasions where we need to find Message rows that # There are occasions where we need to find Message rows that
# have no corresponding UserMessage row, because the user is # have no corresponding UserMessage row, because the user is
@@ -830,16 +828,16 @@ def ok_to_include_history(
include_history = False include_history = False
if narrow is not None: if narrow is not None:
for term in narrow: for term in narrow:
if term["operator"] in channel_operators and not term.get("negated", False): if term.operator in channel_operators and not term.negated:
operand: Union[str, int] = term["operand"] operand: Union[str, int] = term.operand
if isinstance(operand, str): if isinstance(operand, str):
include_history = can_access_stream_history_by_name(user_profile, operand) include_history = can_access_stream_history_by_name(user_profile, operand)
else: else:
include_history = can_access_stream_history_by_id(user_profile, operand) include_history = can_access_stream_history_by_id(user_profile, operand)
elif ( elif (
term["operator"] in channels_operators term.operator in channels_operators
and term["operand"] == "public" and term.operand == "public"
and not term.get("negated", False) and not term.negated
and user_profile.can_access_public_streams() and user_profile.can_access_public_streams()
): ):
include_history = True include_history = True
@@ -847,24 +845,24 @@ def ok_to_include_history(
# that's a property on the UserMessage table. There cannot be # that's a property on the UserMessage table. There cannot be
# historical messages in these cases anyway. # historical messages in these cases anyway.
for term in narrow: for term in narrow:
if term["operator"] == "is" and term["operand"] not in {"resolved", "followed"}: if term.operator == "is" and term.operand not in {"resolved", "followed"}:
include_history = False include_history = False
return include_history return include_history
def get_channel_from_narrow_access_unchecked( def get_channel_from_narrow_access_unchecked(
narrow: OptionalNarrowListT, realm: Realm narrow: Optional[List[NarrowParameter]], realm: Realm
) -> Optional[Stream]: ) -> Optional[Stream]:
if narrow is not None: if narrow is not None:
for term in narrow: for term in narrow:
if term["operator"] in channel_operators: if term.operator in channel_operators:
return get_stream_by_narrow_operand_access_unchecked(term["operand"], realm) return get_stream_by_narrow_operand_access_unchecked(term.operand, realm)
return None return None
def exclude_muting_conditions( def exclude_muting_conditions(
user_profile: UserProfile, narrow: OptionalNarrowListT user_profile: UserProfile, narrow: Optional[List[NarrowParameter]]
) -> List[ClauseElement]: ) -> List[ClauseElement]:
conditions: List[ClauseElement] = [] conditions: List[ClauseElement] = []
channel_id = None channel_id = None
@@ -957,7 +955,7 @@ def add_narrow_conditions(
user_profile: Optional[UserProfile], user_profile: Optional[UserProfile],
inner_msg_id_col: ColumnElement[Integer], inner_msg_id_col: ColumnElement[Integer],
query: Select, query: Select,
narrow: OptionalNarrowListT, narrow: Optional[List[NarrowParameter]],
is_web_public_query: bool, is_web_public_query: bool,
realm: Realm, realm: Realm,
) -> Tuple[Select, bool]: ) -> Tuple[Select, bool]:
@@ -974,15 +972,15 @@ def add_narrow_conditions(
# our query, but we need to collect the search operands and handle # our query, but we need to collect the search operands and handle
# them after the loop. # them after the loop.
for term in narrow: for term in narrow:
if term["operator"] == "search": if term.operator == "search":
search_operands.append(term["operand"]) search_operands.append(term.operand)
else: else:
query = builder.add_term(query, term) query = builder.add_term(query, term)
if search_operands: if search_operands:
is_search = True is_search = True
query = query.add_columns(topic_column_sa(), column("rendered_content", Text)) query = query.add_columns(topic_column_sa(), column("rendered_content", Text))
search_term = dict( search_term = NarrowParameter(
operator="search", operator="search",
operand=" ".join(search_operands), operand=" ".join(search_operands),
) )
@@ -992,7 +990,9 @@ def add_narrow_conditions(
def find_first_unread_anchor( def find_first_unread_anchor(
sa_conn: Connection, user_profile: Optional[UserProfile], narrow: OptionalNarrowListT sa_conn: Connection,
user_profile: Optional[UserProfile],
narrow: Optional[List[NarrowParameter]],
) -> int: ) -> int:
# For anonymous web users, all messages are treated as read, and so # For anonymous web users, all messages are treated as read, and so
# always return LARGER_THAN_MAX_MESSAGE_ID. # always return LARGER_THAN_MAX_MESSAGE_ID.
@@ -1253,7 +1253,7 @@ class FetchedMessages(LimitedMessages[Row]):
def fetch_messages( def fetch_messages(
*, *,
narrow: OptionalNarrowListT, narrow: Optional[List[NarrowParameter]],
user_profile: Optional[UserProfile], user_profile: Optional[UserProfile],
realm: Realm, realm: Realm,
is_web_public_query: bool, is_web_public_query: bool,

View File

@@ -34,6 +34,7 @@ from zerver.lib.narrow import (
LARGER_THAN_MAX_MESSAGE_ID, LARGER_THAN_MAX_MESSAGE_ID,
BadNarrowOperatorError, BadNarrowOperatorError,
NarrowBuilder, NarrowBuilder,
NarrowParameter,
exclude_muting_conditions, exclude_muting_conditions,
find_first_unread_anchor, find_first_unread_anchor,
is_spectator_compatible, is_spectator_compatible,
@@ -122,31 +123,31 @@ class NarrowBuilderTest(ZulipTestCase):
self.othello_email = self.example_user("othello").email self.othello_email = self.example_user("othello").email
def test_add_term_using_not_defined_operator(self) -> None: def test_add_term_using_not_defined_operator(self) -> None:
term = dict(operator="not-defined", operand="any") term = NarrowParameter(operator="not-defined", operand="any")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_channel_operator(self) -> None: def test_add_term_using_channel_operator(self) -> None:
term = dict(operator="channel", operand="Scotland") term = NarrowParameter(operator="channel", operand="Scotland")
self._do_add_term_test(term, "WHERE recipient_id = %(recipient_id_1)s") self._do_add_term_test(term, "WHERE recipient_id = %(recipient_id_1)s")
def test_add_term_using_channel_operator_and_negated(self) -> None: # NEGATED def test_add_term_using_channel_operator_and_negated(self) -> None: # NEGATED
term = dict(operator="channel", operand="Scotland", negated=True) term = NarrowParameter(operator="channel", operand="Scotland", negated=True)
self._do_add_term_test(term, "WHERE recipient_id != %(recipient_id_1)s") self._do_add_term_test(term, "WHERE recipient_id != %(recipient_id_1)s")
def test_add_term_using_channel_operator_and_non_existing_operand_should_raise_error( def test_add_term_using_channel_operator_and_non_existing_operand_should_raise_error(
self, self,
) -> None: # NEGATED ) -> None: # NEGATED
term = dict(operator="channel", operand="non-existing-channel") term = NarrowParameter(operator="channel", operand="non-existing-channel")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_channels_operator_and_invalid_operand_should_raise_error( def test_add_term_using_channels_operator_and_invalid_operand_should_raise_error(
self, self,
) -> None: # NEGATED ) -> None: # NEGATED
term = dict(operator="channels", operand="invalid_operands") term = NarrowParameter(operator="channels", operand="invalid_operands")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_channels_operator_and_public_operand(self) -> None: def test_add_term_using_channels_operator_and_public_operand(self) -> None:
term = dict(operator="channels", operand="public") term = NarrowParameter(operator="channels", operand="public")
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE recipient_id IN (__[POSTCOMPILE_recipient_id_1])", "WHERE recipient_id IN (__[POSTCOMPILE_recipient_id_1])",
@@ -182,7 +183,7 @@ class NarrowBuilderTest(ZulipTestCase):
) )
def test_add_term_using_channels_operator_and_public_operand_negated(self) -> None: def test_add_term_using_channels_operator_and_public_operand_negated(self) -> None:
term = dict(operator="channels", operand="public", negated=True) term = NarrowParameter(operator="channels", operand="public", negated=True)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (recipient_id NOT IN (__[POSTCOMPILE_recipient_id_1]))", "WHERE (recipient_id NOT IN (__[POSTCOMPILE_recipient_id_1]))",
@@ -218,28 +219,28 @@ class NarrowBuilderTest(ZulipTestCase):
) )
def test_add_term_using_is_operator_and_dm_operand(self) -> None: def test_add_term_using_is_operator_and_dm_operand(self) -> None:
term = dict(operator="is", operand="dm") term = NarrowParameter(operator="is", operand="dm")
self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) != %(param_1)s") self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) != %(param_1)s")
def test_add_term_using_is_operator_dm_operand_and_negated(self) -> None: # NEGATED def test_add_term_using_is_operator_dm_operand_and_negated(self) -> None: # NEGATED
term = dict(operator="is", operand="dm", negated=True) term = NarrowParameter(operator="is", operand="dm", negated=True)
self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) = %(param_1)s") self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) = %(param_1)s")
def test_add_term_using_is_operator_and_non_dm_operand(self) -> None: def test_add_term_using_is_operator_and_non_dm_operand(self) -> None:
for operand in ["starred", "mentioned", "alerted"]: for operand in ["starred", "mentioned", "alerted"]:
term = dict(operator="is", operand=operand) term = NarrowParameter(operator="is", operand=operand)
self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) != %(param_1)s") self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) != %(param_1)s")
def test_add_term_using_is_operator_and_unread_operand(self) -> None: def test_add_term_using_is_operator_and_unread_operand(self) -> None:
term = dict(operator="is", operand="unread") term = NarrowParameter(operator="is", operand="unread")
self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) = %(param_1)s") self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) = %(param_1)s")
def test_add_term_using_is_operator_and_unread_operand_and_negated(self) -> None: # NEGATED def test_add_term_using_is_operator_and_unread_operand_and_negated(self) -> None: # NEGATED
term = dict(operator="is", operand="unread", negated=True) term = NarrowParameter(operator="is", operand="unread", negated=True)
self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) != %(param_1)s") self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) != %(param_1)s")
def test_add_term_using_is_operator_non_dm_operand_and_negated(self) -> None: # NEGATED def test_add_term_using_is_operator_non_dm_operand_and_negated(self) -> None: # NEGATED
term = dict(operator="is", operand="starred", negated=True) term = NarrowParameter(operator="is", operand="starred", negated=True)
where_clause = "WHERE (flags & %(flags_1)s) = %(param_1)s" where_clause = "WHERE (flags & %(flags_1)s) = %(param_1)s"
params = dict( params = dict(
flags_1=UserMessage.flags.starred.mask, flags_1=UserMessage.flags.starred.mask,
@@ -247,7 +248,7 @@ class NarrowBuilderTest(ZulipTestCase):
) )
self._do_add_term_test(term, where_clause, params) self._do_add_term_test(term, where_clause, params)
term = dict(operator="is", operand="alerted", negated=True) term = NarrowParameter(operator="is", operand="alerted", negated=True)
where_clause = "WHERE (flags & %(flags_1)s) = %(param_1)s" where_clause = "WHERE (flags & %(flags_1)s) = %(param_1)s"
params = dict( params = dict(
flags_1=UserMessage.flags.has_alert_word.mask, flags_1=UserMessage.flags.has_alert_word.mask,
@@ -255,7 +256,7 @@ class NarrowBuilderTest(ZulipTestCase):
) )
self._do_add_term_test(term, where_clause, params) self._do_add_term_test(term, where_clause, params)
term = dict(operator="is", operand="mentioned", negated=True) term = NarrowParameter(operator="is", operand="mentioned", negated=True)
where_clause = "WHERE (flags & %(flags_1)s) = %(param_1)s" where_clause = "WHERE (flags & %(flags_1)s) = %(param_1)s"
mention_flags_mask = ( mention_flags_mask = (
UserMessage.flags.mentioned.mask UserMessage.flags.mentioned.mask
@@ -270,63 +271,63 @@ class NarrowBuilderTest(ZulipTestCase):
self._do_add_term_test(term, where_clause, params) self._do_add_term_test(term, where_clause, params)
def test_add_term_using_is_operator_for_resolved_topics(self) -> None: def test_add_term_using_is_operator_for_resolved_topics(self) -> None:
term = dict(operator="is", operand="resolved") term = NarrowParameter(operator="is", operand="resolved")
self._do_add_term_test(term, "WHERE (subject LIKE %(subject_1)s || '%%'") self._do_add_term_test(term, "WHERE (subject LIKE %(subject_1)s || '%%'")
def test_add_term_using_is_operator_for_negated_resolved_topics(self) -> None: def test_add_term_using_is_operator_for_negated_resolved_topics(self) -> None:
term = dict(operator="is", operand="resolved", negated=True) term = NarrowParameter(operator="is", operand="resolved", negated=True)
self._do_add_term_test(term, "WHERE (subject NOT LIKE %(subject_1)s || '%%'") self._do_add_term_test(term, "WHERE (subject NOT LIKE %(subject_1)s || '%%'")
def test_add_term_using_is_operator_for_followed_topics(self) -> None: def test_add_term_using_is_operator_for_followed_topics(self) -> None:
term = dict(operator="is", operand="followed", negated=False) term = NarrowParameter(operator="is", operand="followed", negated=False)
self._do_add_term_test( self._do_add_term_test(
term, term,
"EXISTS (SELECT 1 \nFROM zerver_usertopic \nWHERE zerver_usertopic.user_profile_id = %(param_1)s AND zerver_usertopic.visibility_policy = %(param_2)s AND upper(zerver_usertopic.topic_name) = upper(zerver_message.subject) AND zerver_usertopic.recipient_id = zerver_message.recipient_id)", "EXISTS (SELECT 1 \nFROM zerver_usertopic \nWHERE zerver_usertopic.user_profile_id = %(param_1)s AND zerver_usertopic.visibility_policy = %(param_2)s AND upper(zerver_usertopic.topic_name) = upper(zerver_message.subject) AND zerver_usertopic.recipient_id = zerver_message.recipient_id)",
) )
def test_add_term_using_is_operator_for_negated_followed_topics(self) -> None: def test_add_term_using_is_operator_for_negated_followed_topics(self) -> None:
term = dict(operator="is", operand="followed", negated=True) term = NarrowParameter(operator="is", operand="followed", negated=True)
self._do_add_term_test( self._do_add_term_test(
term, term,
"NOT (EXISTS (SELECT 1 \nFROM zerver_usertopic \nWHERE zerver_usertopic.user_profile_id = %(param_1)s AND zerver_usertopic.visibility_policy = %(param_2)s AND upper(zerver_usertopic.topic_name) = upper(zerver_message.subject) AND zerver_usertopic.recipient_id = zerver_message.recipient_id))", "NOT (EXISTS (SELECT 1 \nFROM zerver_usertopic \nWHERE zerver_usertopic.user_profile_id = %(param_1)s AND zerver_usertopic.visibility_policy = %(param_2)s AND upper(zerver_usertopic.topic_name) = upper(zerver_message.subject) AND zerver_usertopic.recipient_id = zerver_message.recipient_id))",
) )
def test_add_term_using_non_supported_operator_should_raise_error(self) -> None: def test_add_term_using_non_supported_operator_should_raise_error(self) -> None:
term = dict(operator="is", operand="non_supported") term = NarrowParameter(operator="is", operand="non_supported")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_topic_operator_and_lunch_operand(self) -> None: def test_add_term_using_topic_operator_and_lunch_operand(self) -> None:
term = dict(operator="topic", operand="lunch") term = NarrowParameter(operator="topic", operand="lunch")
self._do_add_term_test(term, "WHERE upper(subject) = upper(%(param_1)s)") self._do_add_term_test(term, "WHERE upper(subject) = upper(%(param_1)s)")
def test_add_term_using_topic_operator_lunch_operand_and_negated(self) -> None: # NEGATED def test_add_term_using_topic_operator_lunch_operand_and_negated(self) -> None: # NEGATED
term = dict(operator="topic", operand="lunch", negated=True) term = NarrowParameter(operator="topic", operand="lunch", negated=True)
self._do_add_term_test(term, "WHERE upper(subject) != upper(%(param_1)s)") self._do_add_term_test(term, "WHERE upper(subject) != upper(%(param_1)s)")
def test_add_term_using_topic_operator_and_personal_operand(self) -> None: def test_add_term_using_topic_operator_and_personal_operand(self) -> None:
term = dict(operator="topic", operand="personal") term = NarrowParameter(operator="topic", operand="personal")
self._do_add_term_test(term, "WHERE upper(subject) = upper(%(param_1)s)") self._do_add_term_test(term, "WHERE upper(subject) = upper(%(param_1)s)")
def test_add_term_using_topic_operator_personal_operand_and_negated(self) -> None: # NEGATED def test_add_term_using_topic_operator_personal_operand_and_negated(self) -> None: # NEGATED
term = dict(operator="topic", operand="personal", negated=True) term = NarrowParameter(operator="topic", operand="personal", negated=True)
self._do_add_term_test(term, "WHERE upper(subject) != upper(%(param_1)s)") self._do_add_term_test(term, "WHERE upper(subject) != upper(%(param_1)s)")
def test_add_term_using_sender_operator(self) -> None: def test_add_term_using_sender_operator(self) -> None:
term = dict(operator="sender", operand=self.othello_email) term = NarrowParameter(operator="sender", operand=self.othello_email)
self._do_add_term_test(term, "WHERE sender_id = %(param_1)s") self._do_add_term_test(term, "WHERE sender_id = %(param_1)s")
def test_add_term_using_sender_operator_and_negated(self) -> None: # NEGATED def test_add_term_using_sender_operator_and_negated(self) -> None: # NEGATED
term = dict(operator="sender", operand=self.othello_email, negated=True) term = NarrowParameter(operator="sender", operand=self.othello_email, negated=True)
self._do_add_term_test(term, "WHERE sender_id != %(param_1)s") self._do_add_term_test(term, "WHERE sender_id != %(param_1)s")
def test_add_term_using_sender_operator_with_non_existing_user_as_operand( def test_add_term_using_sender_operator_with_non_existing_user_as_operand(
self, self,
) -> None: # NEGATED ) -> None: # NEGATED
term = dict(operator="sender", operand="non-existing@zulip.com") term = NarrowParameter(operator="sender", operand="non-existing@zulip.com")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_dm_operator_and_not_the_same_user_as_operand(self) -> None: def test_add_term_using_dm_operator_and_not_the_same_user_as_operand(self) -> None:
term = dict(operator="dm", operand=self.othello_email) term = NarrowParameter(operator="dm", operand=self.othello_email)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s)", "WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s)",
@@ -336,15 +337,15 @@ class NarrowBuilderTest(ZulipTestCase):
expected_error_message = ( expected_error_message = (
"Invalid narrow operator: No message can be both a channel message and direct message" "Invalid narrow operator: No message can be both a channel message and direct message"
) )
term1 = dict(operator="dm", operand=self.othello_email) term1 = NarrowParameter(operator="dm", operand=self.othello_email)
self._build_query(term1) self._build_query(term1)
topic_term = dict(operator="topic", operand="bogus") topic_term = NarrowParameter(operator="topic", operand="bogus")
with self.assertRaises(BadNarrowOperatorError) as error: with self.assertRaises(BadNarrowOperatorError) as error:
self._build_query(topic_term) self._build_query(topic_term)
self.assertEqual(expected_error_message, str(error.exception)) self.assertEqual(expected_error_message, str(error.exception))
channels_term = dict(operator="channels", operand="public") channels_term = NarrowParameter(operator="channels", operand="public")
with self.assertRaises(BadNarrowOperatorError) as error: with self.assertRaises(BadNarrowOperatorError) as error:
self._build_query(channels_term) self._build_query(channels_term)
self.assertEqual(expected_error_message, str(error.exception)) self.assertEqual(expected_error_message, str(error.exception))
@@ -352,14 +353,14 @@ class NarrowBuilderTest(ZulipTestCase):
def test_add_term_using_dm_operator_not_the_same_user_as_operand_and_negated( def test_add_term_using_dm_operator_not_the_same_user_as_operand_and_negated(
self, self,
) -> None: # NEGATED ) -> None: # NEGATED
term = dict(operator="dm", operand=self.othello_email, negated=True) term = NarrowParameter(operator="dm", operand=self.othello_email, negated=True)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE NOT ((flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s))", "WHERE NOT ((flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s))",
) )
def test_add_term_using_dm_operator_the_same_user_as_operand(self) -> None: def test_add_term_using_dm_operator_the_same_user_as_operand(self) -> None:
term = dict(operator="dm", operand=self.hamlet_email) term = NarrowParameter(operator="dm", operand=self.hamlet_email)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s", "WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s",
@@ -368,7 +369,7 @@ class NarrowBuilderTest(ZulipTestCase):
def test_add_term_using_dm_operator_the_same_user_as_operand_and_negated( def test_add_term_using_dm_operator_the_same_user_as_operand_and_negated(
self, self,
) -> None: # NEGATED ) -> None: # NEGATED
term = dict(operator="dm", operand=self.hamlet_email, negated=True) term = NarrowParameter(operator="dm", operand=self.hamlet_email, negated=True)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE NOT ((flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s)", "WHERE NOT ((flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s)",
@@ -378,7 +379,7 @@ class NarrowBuilderTest(ZulipTestCase):
myself_and_other = ( myself_and_other = (
f"{self.example_user('hamlet').email},{self.example_user('othello').email}" f"{self.example_user('hamlet').email},{self.example_user('othello').email}"
) )
term = dict(operator="dm", operand=myself_and_other) term = NarrowParameter(operator="dm", operand=myself_and_other)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s)", "WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s)",
@@ -387,7 +388,7 @@ class NarrowBuilderTest(ZulipTestCase):
def test_add_term_using_dm_operator_more_than_one_user_as_operand_no_huddle(self) -> None: def test_add_term_using_dm_operator_more_than_one_user_as_operand_no_huddle(self) -> None:
# If the group doesn't exist, it's a flat false # If the group doesn't exist, it's a flat false
two_others = f"{self.example_user('cordelia').email},{self.example_user('othello').email}" two_others = f"{self.example_user('cordelia').email},{self.example_user('othello').email}"
term = dict(operator="dm", operand=two_others) term = NarrowParameter(operator="dm", operand=two_others)
self._do_add_term_test(term, "WHERE false") self._do_add_term_test(term, "WHERE false")
def test_add_term_using_dm_operator_more_than_one_user_as_operand(self) -> None: def test_add_term_using_dm_operator_more_than_one_user_as_operand(self) -> None:
@@ -400,7 +401,7 @@ class NarrowBuilderTest(ZulipTestCase):
] ]
) )
two_others = f"{self.example_user('cordelia').email},{self.example_user('othello').email}" two_others = f"{self.example_user('cordelia').email},{self.example_user('othello').email}"
term = dict(operator="dm", operand=two_others) term = NarrowParameter(operator="dm", operand=two_others)
self._do_add_term_test(term, "WHERE recipient_id = %(recipient_id_1)s") self._do_add_term_test(term, "WHERE recipient_id = %(recipient_id_1)s")
def test_add_term_using_dm_operator_self_and_user_as_operand_and_negated( def test_add_term_using_dm_operator_self_and_user_as_operand_and_negated(
@@ -409,7 +410,7 @@ class NarrowBuilderTest(ZulipTestCase):
myself_and_other = ( myself_and_other = (
f"{self.example_user('hamlet').email},{self.example_user('othello').email}" f"{self.example_user('hamlet').email},{self.example_user('othello').email}"
) )
term = dict(operator="dm", operand=myself_and_other, negated=True) term = NarrowParameter(operator="dm", operand=myself_and_other, negated=True)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE NOT ((flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s))", "WHERE NOT ((flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s))",
@@ -420,7 +421,7 @@ class NarrowBuilderTest(ZulipTestCase):
) -> None: # NEGATED ) -> None: # NEGATED
# If the group doesn't exist, it's a flat true # If the group doesn't exist, it's a flat true
two_others = f"{self.example_user('cordelia').email},{self.example_user('othello').email}" two_others = f"{self.example_user('cordelia').email},{self.example_user('othello').email}"
term = dict(operator="dm", operand=two_others, negated=True) term = NarrowParameter(operator="dm", operand=two_others, negated=True)
self._do_add_term_test(term, "WHERE true") self._do_add_term_test(term, "WHERE true")
def test_add_term_using_dm_operator_more_than_one_user_as_operand_and_negated( def test_add_term_using_dm_operator_more_than_one_user_as_operand_and_negated(
@@ -435,26 +436,28 @@ class NarrowBuilderTest(ZulipTestCase):
] ]
) )
two_others = f"{self.example_user('cordelia').email},{self.example_user('othello').email}" two_others = f"{self.example_user('cordelia').email},{self.example_user('othello').email}"
term = dict(operator="dm", operand=two_others, negated=True) term = NarrowParameter(operator="dm", operand=two_others, negated=True)
self._do_add_term_test(term, "WHERE recipient_id != %(recipient_id_1)s") self._do_add_term_test(term, "WHERE recipient_id != %(recipient_id_1)s")
def test_add_term_using_dm_operator_with_comma_noise(self) -> None: def test_add_term_using_dm_operator_with_comma_noise(self) -> None:
term = dict(operator="dm", operand=" ,,, ,,, ,") term = NarrowParameter(operator="dm", operand=" ,,, ,,, ,")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_dm_operator_with_existing_and_non_existing_user_as_operand( def test_add_term_using_dm_operator_with_existing_and_non_existing_user_as_operand(
self, self,
) -> None: ) -> None:
term = dict(operator="dm", operand=self.othello_email + ",non-existing@zulip.com") term = NarrowParameter(
operator="dm", operand=self.othello_email + ",non-existing@zulip.com"
)
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_dm_including_operator_with_logged_in_user_email(self) -> None: def test_add_term_using_dm_including_operator_with_logged_in_user_email(self) -> None:
term = dict(operator="dm-including", operand=self.hamlet_email) term = NarrowParameter(operator="dm-including", operand=self.hamlet_email)
self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) != %(param_1)s") self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) != %(param_1)s")
def test_add_term_using_dm_including_operator_with_different_user_email(self) -> None: def test_add_term_using_dm_including_operator_with_different_user_email(self) -> None:
# Test without any such group direct messages existing # Test without any such group direct messages existing
term = dict(operator="dm-including", operand=self.othello_email) term = NarrowParameter(operator="dm-including", operand=self.othello_email)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s OR recipient_id IN (__[POSTCOMPILE_recipient_id_3]))", "WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s OR recipient_id IN (__[POSTCOMPILE_recipient_id_3]))",
@@ -465,7 +468,7 @@ class NarrowBuilderTest(ZulipTestCase):
self.user_profile, [self.example_user("othello"), self.example_user("cordelia")] self.user_profile, [self.example_user("othello"), self.example_user("cordelia")]
) )
term = dict(operator="dm-including", operand=self.othello_email) term = NarrowParameter(operator="dm-including", operand=self.othello_email)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s OR recipient_id IN (__[POSTCOMPILE_recipient_id_3]))", "WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s OR recipient_id IN (__[POSTCOMPILE_recipient_id_3]))",
@@ -474,37 +477,37 @@ class NarrowBuilderTest(ZulipTestCase):
def test_add_term_using_dm_including_operator_with_different_user_email_and_negated( def test_add_term_using_dm_including_operator_with_different_user_email_and_negated(
self, self,
) -> None: # NEGATED ) -> None: # NEGATED
term = dict(operator="dm-including", operand=self.othello_email, negated=True) term = NarrowParameter(operator="dm-including", operand=self.othello_email, negated=True)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE NOT ((flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s OR recipient_id IN (__[POSTCOMPILE_recipient_id_3])))", "WHERE NOT ((flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND (sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s OR sender_id = %(sender_id_2)s AND recipient_id = %(recipient_id_2)s OR recipient_id IN (__[POSTCOMPILE_recipient_id_3])))",
) )
def test_add_term_using_id_operator_integer(self) -> None: def test_add_term_using_id_operator_integer(self) -> None:
term = dict(operator="id", operand=555) term = NarrowParameter(operator="id", operand=555)
self._do_add_term_test(term, "WHERE id = %(param_1)s") self._do_add_term_test(term, "WHERE id = %(param_1)s")
def test_add_term_using_id_operator_string(self) -> None: def test_add_term_using_id_operator_string(self) -> None:
term = dict(operator="id", operand="555") term = NarrowParameter(operator="id", operand="555")
self._do_add_term_test(term, "WHERE id = %(param_1)s") self._do_add_term_test(term, "WHERE id = %(param_1)s")
def test_add_term_using_id_operator_invalid(self) -> None: def test_add_term_using_id_operator_invalid(self) -> None:
term = dict(operator="id", operand="") term = NarrowParameter(operator="id", operand="")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
term = dict(operator="id", operand="notanint") term = NarrowParameter(operator="id", operand="notanint")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
term = dict(operator="id", operand=str(Message.MAX_POSSIBLE_MESSAGE_ID + 1)) term = NarrowParameter(operator="id", operand=str(Message.MAX_POSSIBLE_MESSAGE_ID + 1))
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_id_operator_and_negated(self) -> None: # NEGATED def test_add_term_using_id_operator_and_negated(self) -> None: # NEGATED
term = dict(operator="id", operand=555, negated=True) term = NarrowParameter(operator="id", operand=555, negated=True)
self._do_add_term_test(term, "WHERE id != %(param_1)s") self._do_add_term_test(term, "WHERE id != %(param_1)s")
@override_settings(USING_PGROONGA=False) @override_settings(USING_PGROONGA=False)
def test_add_term_using_search_operator(self) -> None: def test_add_term_using_search_operator(self) -> None:
term = dict(operator="search", operand='"french fries"') term = NarrowParameter(operator="search", operand='"french fries"')
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (content ILIKE %(content_1)s OR subject ILIKE %(subject_1)s) AND (search_tsvector @@ plainto_tsquery(%(param_4)s, %(param_5)s))", "WHERE (content ILIKE %(content_1)s OR subject ILIKE %(subject_1)s) AND (search_tsvector @@ plainto_tsquery(%(param_4)s, %(param_5)s))",
@@ -512,7 +515,7 @@ class NarrowBuilderTest(ZulipTestCase):
@override_settings(USING_PGROONGA=False) @override_settings(USING_PGROONGA=False)
def test_add_term_using_search_operator_and_negated(self) -> None: # NEGATED def test_add_term_using_search_operator_and_negated(self) -> None: # NEGATED
term = dict(operator="search", operand='"french fries"', negated=True) term = NarrowParameter(operator="search", operand='"french fries"', negated=True)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE NOT (content ILIKE %(content_1)s OR subject ILIKE %(subject_1)s) AND NOT (search_tsvector @@ plainto_tsquery(%(param_4)s, %(param_5)s))", "WHERE NOT (content ILIKE %(content_1)s OR subject ILIKE %(subject_1)s) AND NOT (search_tsvector @@ plainto_tsquery(%(param_4)s, %(param_5)s))",
@@ -520,113 +523,113 @@ class NarrowBuilderTest(ZulipTestCase):
@override_settings(USING_PGROONGA=True) @override_settings(USING_PGROONGA=True)
def test_add_term_using_search_operator_pgroonga(self) -> None: def test_add_term_using_search_operator_pgroonga(self) -> None:
term = dict(operator="search", operand='"french fries"') term = NarrowParameter(operator="search", operand='"french fries"')
self._do_add_term_test(term, "WHERE search_pgroonga &@~ escape_html(%(escape_html_1)s)") self._do_add_term_test(term, "WHERE search_pgroonga &@~ escape_html(%(escape_html_1)s)")
@override_settings(USING_PGROONGA=True) @override_settings(USING_PGROONGA=True)
def test_add_term_using_search_operator_and_negated_pgroonga(self) -> None: # NEGATED def test_add_term_using_search_operator_and_negated_pgroonga(self) -> None: # NEGATED
term = dict(operator="search", operand='"french fries"', negated=True) term = NarrowParameter(operator="search", operand='"french fries"', negated=True)
self._do_add_term_test( self._do_add_term_test(
term, "WHERE NOT (search_pgroonga &@~ escape_html(%(escape_html_1)s))" term, "WHERE NOT (search_pgroonga &@~ escape_html(%(escape_html_1)s))"
) )
def test_add_term_using_has_operator_and_attachment_operand(self) -> None: def test_add_term_using_has_operator_and_attachment_operand(self) -> None:
term = dict(operator="has", operand="attachment") term = NarrowParameter(operator="has", operand="attachment")
self._do_add_term_test(term, "WHERE has_attachment") self._do_add_term_test(term, "WHERE has_attachment")
def test_add_term_using_has_operator_attachment_operand_and_negated(self) -> None: # NEGATED def test_add_term_using_has_operator_attachment_operand_and_negated(self) -> None: # NEGATED
term = dict(operator="has", operand="attachment", negated=True) term = NarrowParameter(operator="has", operand="attachment", negated=True)
self._do_add_term_test(term, "WHERE NOT has_attachment") self._do_add_term_test(term, "WHERE NOT has_attachment")
def test_add_term_using_has_operator_and_image_operand(self) -> None: def test_add_term_using_has_operator_and_image_operand(self) -> None:
term = dict(operator="has", operand="image") term = NarrowParameter(operator="has", operand="image")
self._do_add_term_test(term, "WHERE has_image") self._do_add_term_test(term, "WHERE has_image")
def test_add_term_using_has_operator_image_operand_and_negated(self) -> None: # NEGATED def test_add_term_using_has_operator_image_operand_and_negated(self) -> None: # NEGATED
term = dict(operator="has", operand="image", negated=True) term = NarrowParameter(operator="has", operand="image", negated=True)
self._do_add_term_test(term, "WHERE NOT has_image") self._do_add_term_test(term, "WHERE NOT has_image")
def test_add_term_using_has_operator_and_link_operand(self) -> None: def test_add_term_using_has_operator_and_link_operand(self) -> None:
term = dict(operator="has", operand="link") term = NarrowParameter(operator="has", operand="link")
self._do_add_term_test(term, "WHERE has_link") self._do_add_term_test(term, "WHERE has_link")
def test_add_term_using_has_operator_link_operand_and_negated(self) -> None: # NEGATED def test_add_term_using_has_operator_link_operand_and_negated(self) -> None: # NEGATED
term = dict(operator="has", operand="link", negated=True) term = NarrowParameter(operator="has", operand="link", negated=True)
self._do_add_term_test(term, "WHERE NOT has_link") self._do_add_term_test(term, "WHERE NOT has_link")
def test_add_term_using_has_operator_and_reaction_operand(self) -> None: def test_add_term_using_has_operator_and_reaction_operand(self) -> None:
term = dict(operator="has", operand="reaction") term = NarrowParameter(operator="has", operand="reaction")
self._do_add_term_test( self._do_add_term_test(
term, term,
"EXISTS (SELECT 1 \nFROM zerver_reaction \nWHERE zerver_message.id = zerver_reaction.message_id)", "EXISTS (SELECT 1 \nFROM zerver_reaction \nWHERE zerver_message.id = zerver_reaction.message_id)",
) )
def test_add_term_using_has_operator_and_reaction_operand_and_negated(self) -> None: def test_add_term_using_has_operator_and_reaction_operand_and_negated(self) -> None:
term = dict(operator="has", operand="reaction", negated=True) term = NarrowParameter(operator="has", operand="reaction", negated=True)
self._do_add_term_test( self._do_add_term_test(
term, term,
"NOT (EXISTS (SELECT 1 \nFROM zerver_reaction \nWHERE zerver_message.id = zerver_reaction.message_id))", "NOT (EXISTS (SELECT 1 \nFROM zerver_reaction \nWHERE zerver_message.id = zerver_reaction.message_id))",
) )
def test_add_term_using_has_operator_non_supported_operand_should_raise_error(self) -> None: def test_add_term_using_has_operator_non_supported_operand_should_raise_error(self) -> None:
term = dict(operator="has", operand="non_supported") term = NarrowParameter(operator="has", operand="non_supported")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_in_operator(self) -> None: def test_add_term_using_in_operator(self) -> None:
mute_channel(self.realm, self.user_profile, "Verona") mute_channel(self.realm, self.user_profile, "Verona")
term = dict(operator="in", operand="home") term = NarrowParameter(operator="in", operand="home")
self._do_add_term_test(term, "WHERE (recipient_id NOT IN (__[POSTCOMPILE_recipient_id_1]))") self._do_add_term_test(term, "WHERE (recipient_id NOT IN (__[POSTCOMPILE_recipient_id_1]))")
def test_add_term_using_in_operator_and_negated(self) -> None: def test_add_term_using_in_operator_and_negated(self) -> None:
# negated = True should not change anything # negated = True should not change anything
mute_channel(self.realm, self.user_profile, "Verona") mute_channel(self.realm, self.user_profile, "Verona")
term = dict(operator="in", operand="home", negated=True) term = NarrowParameter(operator="in", operand="home", negated=True)
self._do_add_term_test(term, "WHERE (recipient_id NOT IN (__[POSTCOMPILE_recipient_id_1]))") self._do_add_term_test(term, "WHERE (recipient_id NOT IN (__[POSTCOMPILE_recipient_id_1]))")
def test_add_term_using_in_operator_and_all_operand(self) -> None: def test_add_term_using_in_operator_and_all_operand(self) -> None:
mute_channel(self.realm, self.user_profile, "Verona") mute_channel(self.realm, self.user_profile, "Verona")
term = dict(operator="in", operand="all") term = NarrowParameter(operator="in", operand="all")
query = self._build_query(term) query = self._build_query(term)
self.assertEqual(get_sqlalchemy_sql(query), "SELECT id \nFROM zerver_message") self.assertEqual(get_sqlalchemy_sql(query), "SELECT id \nFROM zerver_message")
def test_add_term_using_in_operator_all_operand_and_negated(self) -> None: def test_add_term_using_in_operator_all_operand_and_negated(self) -> None:
# negated = True should not change anything # negated = True should not change anything
mute_channel(self.realm, self.user_profile, "Verona") mute_channel(self.realm, self.user_profile, "Verona")
term = dict(operator="in", operand="all", negated=True) term = NarrowParameter(operator="in", operand="all", negated=True)
query = self._build_query(term) query = self._build_query(term)
self.assertEqual(get_sqlalchemy_sql(query), "SELECT id \nFROM zerver_message") self.assertEqual(get_sqlalchemy_sql(query), "SELECT id \nFROM zerver_message")
def test_add_term_using_in_operator_and_not_defined_operand(self) -> None: def test_add_term_using_in_operator_and_not_defined_operand(self) -> None:
term = dict(operator="in", operand="not_defined") term = NarrowParameter(operator="in", operand="not_defined")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_near_operator(self) -> None: def test_add_term_using_near_operator(self) -> None:
term = dict(operator="near", operand="operand") term = NarrowParameter(operator="near", operand="operand")
query = self._build_query(term) query = self._build_query(term)
self.assertEqual(get_sqlalchemy_sql(query), "SELECT id \nFROM zerver_message") self.assertEqual(get_sqlalchemy_sql(query), "SELECT id \nFROM zerver_message")
def test_add_term_non_web_public_channel_in_web_public_query(self) -> None: def test_add_term_non_web_public_channel_in_web_public_query(self) -> None:
self.make_stream("non-web-public-channel", realm=self.realm) self.make_stream("non-web-public-channel", realm=self.realm)
term = dict(operator="channel", operand="non-web-public-channel") term = NarrowParameter(operator="channel", operand="non-web-public-channel")
builder = NarrowBuilder(self.user_profile, column("id", Integer), self.realm, True) builder = NarrowBuilder(self.user_profile, column("id", Integer), self.realm, True)
def _build_query(term: Dict[str, Any]) -> Select: def _build_query(term: NarrowParameter) -> Select:
return builder.add_term(self.raw_query, term) return builder.add_term(self.raw_query, term)
self.assertRaises(BadNarrowOperatorError, _build_query, term) self.assertRaises(BadNarrowOperatorError, _build_query, term)
# Test "is:private" (legacy alias for "is:dm") # Test "is:private" (legacy alias for "is:dm")
def test_add_term_using_is_operator_and_private_operand(self) -> None: def test_add_term_using_is_operator_and_private_operand(self) -> None:
term = dict(operator="is", operand="private") term = NarrowParameter(operator="is", operand="private")
self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) != %(param_1)s") self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) != %(param_1)s")
def test_add_term_using_is_operator_private_operand_and_negated(self) -> None: # NEGATED def test_add_term_using_is_operator_private_operand_and_negated(self) -> None: # NEGATED
term = dict(operator="is", operand="private", negated=True) term = NarrowParameter(operator="is", operand="private", negated=True)
self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) = %(param_1)s") self._do_add_term_test(term, "WHERE (flags & %(flags_1)s) = %(param_1)s")
# Test that "pm-with" (legacy alias for "dm") works. # Test that "pm-with" (legacy alias for "dm") works.
def test_add_term_using_pm_with_operator(self) -> None: def test_add_term_using_pm_with_operator(self) -> None:
term = dict(operator="pm-with", operand=self.hamlet_email) term = NarrowParameter(operator="pm-with", operand=self.hamlet_email)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s", "WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s",
@@ -634,7 +637,7 @@ class NarrowBuilderTest(ZulipTestCase):
# Test that the underscore version of "pm-with" works. # Test that the underscore version of "pm-with" works.
def test_add_term_using_underscore_version_of_pm_with_operator(self) -> None: def test_add_term_using_underscore_version_of_pm_with_operator(self) -> None:
term = dict(operator="pm_with", operand=self.hamlet_email) term = NarrowParameter(operator="pm_with", operand=self.hamlet_email)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s", "WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND sender_id = %(sender_id_1)s AND recipient_id = %(recipient_id_1)s",
@@ -642,11 +645,11 @@ class NarrowBuilderTest(ZulipTestCase):
# Test that deprecated "group-pm-with" (replaced by "dm-including" ) works. # Test that deprecated "group-pm-with" (replaced by "dm-including" ) works.
def test_add_term_using_dm_including_operator_with_non_existing_user(self) -> None: def test_add_term_using_dm_including_operator_with_non_existing_user(self) -> None:
term = dict(operator="dm-including", operand="non-existing@zulip.com") term = NarrowParameter(operator="dm-including", operand="non-existing@zulip.com")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_group_pm_operator_and_not_the_same_user_as_operand(self) -> None: def test_add_term_using_group_pm_operator_and_not_the_same_user_as_operand(self) -> None:
term = dict(operator="group-pm-with", operand=self.othello_email) term = NarrowParameter(operator="group-pm-with", operand=self.othello_email)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND recipient_id IN (__[POSTCOMPILE_recipient_id_1])", "WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND recipient_id IN (__[POSTCOMPILE_recipient_id_1])",
@@ -655,19 +658,19 @@ class NarrowBuilderTest(ZulipTestCase):
def test_add_term_using_group_pm_operator_not_the_same_user_as_operand_and_negated( def test_add_term_using_group_pm_operator_not_the_same_user_as_operand_and_negated(
self, self,
) -> None: # NEGATED ) -> None: # NEGATED
term = dict(operator="group-pm-with", operand=self.othello_email, negated=True) term = NarrowParameter(operator="group-pm-with", operand=self.othello_email, negated=True)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE NOT ((flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND recipient_id IN (__[POSTCOMPILE_recipient_id_1]))", "WHERE NOT ((flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND recipient_id IN (__[POSTCOMPILE_recipient_id_1]))",
) )
def test_add_term_using_group_pm_operator_with_non_existing_user_as_operand(self) -> None: def test_add_term_using_group_pm_operator_with_non_existing_user_as_operand(self) -> None:
term = dict(operator="group-pm-with", operand="non-existing@zulip.com") term = NarrowParameter(operator="group-pm-with", operand="non-existing@zulip.com")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
# Test that the underscore version of "group-pm-with" works. # Test that the underscore version of "group-pm-with" works.
def test_add_term_using_underscore_version_of_group_pm_with_operator(self) -> None: def test_add_term_using_underscore_version_of_group_pm_with_operator(self) -> None:
term = dict(operator="group_pm_with", operand=self.othello_email) term = NarrowParameter(operator="group_pm_with", operand=self.othello_email)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND recipient_id IN (__[POSTCOMPILE_recipient_id_1])", "WHERE (flags & %(flags_1)s) != %(param_1)s AND realm_id = %(realm_id_1)s AND recipient_id IN (__[POSTCOMPILE_recipient_id_1])",
@@ -675,42 +678,42 @@ class NarrowBuilderTest(ZulipTestCase):
# Test that "stream" (legacy alias for "channel" operator) works. # Test that "stream" (legacy alias for "channel" operator) works.
def test_add_term_using_stream_operator(self) -> None: def test_add_term_using_stream_operator(self) -> None:
term = dict(operator="stream", operand="Scotland") term = NarrowParameter(operator="stream", operand="Scotland")
self._do_add_term_test(term, "WHERE recipient_id = %(recipient_id_1)s") self._do_add_term_test(term, "WHERE recipient_id = %(recipient_id_1)s")
def test_add_term_using_stream_operator_and_negated(self) -> None: # NEGATED def test_add_term_using_stream_operator_and_negated(self) -> None: # NEGATED
term = dict(operator="stream", operand="Scotland", negated=True) term = NarrowParameter(operator="stream", operand="Scotland", negated=True)
self._do_add_term_test(term, "WHERE recipient_id != %(recipient_id_1)s") self._do_add_term_test(term, "WHERE recipient_id != %(recipient_id_1)s")
def test_add_term_using_stream_operator_and_non_existing_operand_should_raise_error( def test_add_term_using_stream_operator_and_non_existing_operand_should_raise_error(
self, self,
) -> None: # NEGATED ) -> None: # NEGATED
term = dict(operator="stream", operand="non-existing-channel") term = NarrowParameter(operator="stream", operand="non-existing-channel")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
# Test that "streams" (legacy alias for "channels" operator) works. # Test that "streams" (legacy alias for "channels" operator) works.
def test_add_term_using_streams_operator_and_invalid_operand_should_raise_error( def test_add_term_using_streams_operator_and_invalid_operand_should_raise_error(
self, self,
) -> None: # NEGATED ) -> None: # NEGATED
term = dict(operator="streams", operand="invalid_operands") term = NarrowParameter(operator="streams", operand="invalid_operands")
self.assertRaises(BadNarrowOperatorError, self._build_query, term) self.assertRaises(BadNarrowOperatorError, self._build_query, term)
def test_add_term_using_streams_operator_and_public_operand(self) -> None: def test_add_term_using_streams_operator_and_public_operand(self) -> None:
term = dict(operator="streams", operand="public") term = NarrowParameter(operator="streams", operand="public")
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE recipient_id IN (__[POSTCOMPILE_recipient_id_1])", "WHERE recipient_id IN (__[POSTCOMPILE_recipient_id_1])",
) )
def test_add_term_using_streams_operator_and_public_operand_negated(self) -> None: def test_add_term_using_streams_operator_and_public_operand_negated(self) -> None:
term = dict(operator="streams", operand="public", negated=True) term = NarrowParameter(operator="streams", operand="public", negated=True)
self._do_add_term_test( self._do_add_term_test(
term, term,
"WHERE (recipient_id NOT IN (__[POSTCOMPILE_recipient_id_1]))", "WHERE (recipient_id NOT IN (__[POSTCOMPILE_recipient_id_1]))",
) )
def _do_add_term_test( def _do_add_term_test(
self, term: Dict[str, Any], where_clause: str, params: Optional[Dict[str, Any]] = None self, term: NarrowParameter, where_clause: str, params: Optional[Dict[str, Any]] = None
) -> None: ) -> None:
query = self._build_query(term) query = self._build_query(term)
if params is not None: if params is not None:
@@ -718,7 +721,7 @@ class NarrowBuilderTest(ZulipTestCase):
self.assertEqual(actual_params, params) self.assertEqual(actual_params, params)
self.assertIn(where_clause, get_sqlalchemy_sql(query)) self.assertIn(where_clause, get_sqlalchemy_sql(query))
def _build_query(self, term: Dict[str, Any]) -> Select: def _build_query(self, term: NarrowParameter) -> Select:
return self.builder.add_term(self.raw_query, term) return self.builder.add_term(self.raw_query, term)
@@ -977,62 +980,86 @@ class NarrowLibraryTest(ZulipTestCase):
def test_is_spectator_compatible(self) -> None: def test_is_spectator_compatible(self) -> None:
self.assertTrue(is_spectator_compatible([])) self.assertTrue(is_spectator_compatible([]))
self.assertTrue(is_spectator_compatible([{"operator": "has", "operand": "attachment"}]))
self.assertTrue(is_spectator_compatible([{"operator": "has", "operand": "image"}]))
self.assertTrue(is_spectator_compatible([{"operator": "search", "operand": "magic"}]))
self.assertTrue(is_spectator_compatible([{"operator": "near", "operand": "15"}]))
self.assertTrue( self.assertTrue(
is_spectator_compatible( is_spectator_compatible([NarrowParameter(operator="has", operand="attachment")])
[{"operator": "id", "operand": "15"}, {"operator": "has", "operand": "attachment"}]
)
) )
self.assertTrue(is_spectator_compatible([NarrowParameter(operator="has", operand="image")]))
self.assertTrue( self.assertTrue(
is_spectator_compatible([{"operator": "sender", "operand": "hamlet@zulip.com"}]) is_spectator_compatible([NarrowParameter(operator="search", operand="magic")])
) )
self.assertFalse( self.assertTrue(is_spectator_compatible([NarrowParameter(operator="near", operand="15")]))
is_spectator_compatible([{"operator": "dm", "operand": "hamlet@zulip.com"}])
)
self.assertFalse(
is_spectator_compatible([{"operator": "dm-including", "operand": "hamlet@zulip.com"}])
)
self.assertTrue(is_spectator_compatible([{"operator": "channel", "operand": "Denmark"}]))
self.assertTrue( self.assertTrue(
is_spectator_compatible( is_spectator_compatible(
[ [
{"operator": "channel", "operand": "Denmark"}, NarrowParameter(operator="id", operand="15"),
{"operator": "topic", "operand": "logic"}, NarrowParameter(operator="has", operand="attachment"),
] ]
) )
) )
self.assertFalse(is_spectator_compatible([{"operator": "is", "operand": "starred"}])) self.assertTrue(
self.assertFalse(is_spectator_compatible([{"operator": "is", "operand": "dm"}])) is_spectator_compatible(
self.assertTrue(is_spectator_compatible([{"operator": "channels", "operand": "public"}])) [NarrowParameter(operator="sender", operand="hamlet@zulip.com")]
# Malformed input not allowed
self.assertFalse(is_spectator_compatible([{"operator": "has"}]))
# "is:private" is a legacy alias for "is:dm".
self.assertFalse(is_spectator_compatible([{"operator": "is", "operand": "private"}]))
# "pm-with:"" is a legacy alias for "dm:"
self.assertFalse(
is_spectator_compatible([{"operator": "pm-with", "operand": "hamlet@zulip.com"}])
) )
# "group-pm-with:" was deprecated by the addition of "dm-including:"
self.assertFalse(
is_spectator_compatible([{"operator": "group-pm-with", "operand": "hamlet@zulip.com"}])
) )
# "stream" is a legacy alias for "channel" operator self.assertFalse(
self.assertTrue(is_spectator_compatible([{"operator": "stream", "operand": "Denmark"}])) is_spectator_compatible([NarrowParameter(operator="dm", operand="hamlet@zulip.com")])
)
self.assertFalse(
is_spectator_compatible(
[NarrowParameter(operator="dm-including", operand="hamlet@zulip.com")]
)
)
self.assertTrue(
is_spectator_compatible([NarrowParameter(operator="channel", operand="Denmark")])
)
self.assertTrue( self.assertTrue(
is_spectator_compatible( is_spectator_compatible(
[ [
{"operator": "stream", "operand": "Denmark"}, NarrowParameter(operator="channel", operand="Denmark"),
{"operator": "topic", "operand": "logic"}, NarrowParameter(operator="topic", operand="logic"),
]
)
)
self.assertFalse(
is_spectator_compatible([NarrowParameter(operator="is", operand="starred")])
)
self.assertFalse(is_spectator_compatible([NarrowParameter(operator="is", operand="dm")]))
self.assertTrue(
is_spectator_compatible([NarrowParameter(operator="channels", operand="public")])
)
# "is:private" is a legacy alias for "is:dm".
self.assertFalse(
is_spectator_compatible([NarrowParameter(operator="is", operand="private")])
)
# "pm-with:"" is a legacy alias for "dm:"
self.assertFalse(
is_spectator_compatible(
[NarrowParameter(operator="pm-with", operand="hamlet@zulip.com")]
)
)
# "group-pm-with:" was deprecated by the addition of "dm-including:"
self.assertFalse(
is_spectator_compatible(
[NarrowParameter(operator="group-pm-with", operand="hamlet@zulip.com")]
)
)
# "stream" is a legacy alias for "channel" operator
self.assertTrue(
is_spectator_compatible([NarrowParameter(operator="stream", operand="Denmark")])
)
self.assertTrue(
is_spectator_compatible(
[
NarrowParameter(operator="stream", operand="Denmark"),
NarrowParameter(operator="topic", operand="logic"),
] ]
) )
) )
# "streams" is a legacy alias for "channels" operator # "streams" is a legacy alias for "channels" operator
self.assertTrue(is_spectator_compatible([{"operator": "streams", "operand": "public"}])) self.assertTrue(
is_spectator_compatible([NarrowParameter(operator="streams", operand="public")])
)
class IncludeHistoryTest(ZulipTestCase): class IncludeHistoryTest(ZulipTestCase):
@@ -1042,19 +1069,19 @@ class IncludeHistoryTest(ZulipTestCase):
# Negated channel searches should not include history. # Negated channel searches should not include history.
narrow = [ narrow = [
dict(operator="channel", operand="public_channel", negated=True), NarrowParameter(operator="channel", operand="public_channel", negated=True),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
# channels:public searches should include history for non-guest members. # channels:public searches should include history for non-guest members.
narrow = [ narrow = [
dict(operator="channels", operand="public"), NarrowParameter(operator="channels", operand="public"),
] ]
self.assertTrue(ok_to_include_history(narrow, user_profile, False)) self.assertTrue(ok_to_include_history(narrow, user_profile, False))
# Negated -channels:public searches should not include history. # Negated -channels:public searches should not include history.
narrow = [ narrow = [
dict(operator="channels", operand="public", negated=True), NarrowParameter(operator="channels", operand="public", negated=True),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
@@ -1063,7 +1090,7 @@ class IncludeHistoryTest(ZulipTestCase):
subscribed_user_profile = self.example_user("cordelia") subscribed_user_profile = self.example_user("cordelia")
self.subscribe(subscribed_user_profile, "private_channel") self.subscribe(subscribed_user_profile, "private_channel")
narrow = [ narrow = [
dict(operator="channel", operand="private_channel"), NarrowParameter(operator="channel", operand="private_channel"),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
@@ -1078,69 +1105,69 @@ class IncludeHistoryTest(ZulipTestCase):
subscribed_user_profile = self.example_user("cordelia") subscribed_user_profile = self.example_user("cordelia")
self.subscribe(subscribed_user_profile, "private_channel_2") self.subscribe(subscribed_user_profile, "private_channel_2")
narrow = [ narrow = [
dict(operator="channel", operand="private_channel_2"), NarrowParameter(operator="channel", operand="private_channel_2"),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
self.assertTrue(ok_to_include_history(narrow, subscribed_user_profile, False)) self.assertTrue(ok_to_include_history(narrow, subscribed_user_profile, False))
# History doesn't apply to direct messages. # History doesn't apply to direct messages.
narrow = [ narrow = [
dict(operator="is", operand="dm"), NarrowParameter(operator="is", operand="dm"),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
# "is:private" is a legacy alias for "is:dm". # "is:private" is a legacy alias for "is:dm".
narrow = [ narrow = [
dict(operator="is", operand="private"), NarrowParameter(operator="is", operand="private"),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
# History doesn't apply to unread messages. # History doesn't apply to unread messages.
narrow = [ narrow = [
dict(operator="is", operand="unread"), NarrowParameter(operator="is", operand="unread"),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
# If we are looking for something like starred messages, there is # If we are looking for something like starred messages, there is
# no point in searching historical messages. # no point in searching historical messages.
narrow = [ narrow = [
dict(operator="channel", operand="public_channel"), NarrowParameter(operator="channel", operand="public_channel"),
dict(operator="is", operand="starred"), NarrowParameter(operator="is", operand="starred"),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
# No point in searching history for is operator even if included with # No point in searching history for is operator even if included with
# channels:public # channels:public
narrow = [ narrow = [
dict(operator="channels", operand="public"), NarrowParameter(operator="channels", operand="public"),
dict(operator="is", operand="mentioned"), NarrowParameter(operator="is", operand="mentioned"),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
narrow = [ narrow = [
dict(operator="channels", operand="public"), NarrowParameter(operator="channels", operand="public"),
dict(operator="is", operand="unread"), NarrowParameter(operator="is", operand="unread"),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
narrow = [ narrow = [
dict(operator="channels", operand="public"), NarrowParameter(operator="channels", operand="public"),
dict(operator="is", operand="alerted"), NarrowParameter(operator="is", operand="alerted"),
] ]
self.assertFalse(ok_to_include_history(narrow, user_profile, False)) self.assertFalse(ok_to_include_history(narrow, user_profile, False))
narrow = [ narrow = [
dict(operator="channels", operand="public"), NarrowParameter(operator="channels", operand="public"),
dict(operator="is", operand="resolved"), NarrowParameter(operator="is", operand="resolved"),
] ]
self.assertTrue(ok_to_include_history(narrow, user_profile, False)) self.assertTrue(ok_to_include_history(narrow, user_profile, False))
# simple True case # simple True case
narrow = [ narrow = [
dict(operator="channel", operand="public_channel"), NarrowParameter(operator="channel", operand="public_channel"),
] ]
self.assertTrue(ok_to_include_history(narrow, user_profile, False)) self.assertTrue(ok_to_include_history(narrow, user_profile, False))
narrow = [ narrow = [
dict(operator="channel", operand="public_channel"), NarrowParameter(operator="channel", operand="public_channel"),
dict(operator="topic", operand="whatever"), NarrowParameter(operator="topic", operand="whatever"),
dict(operator="search", operand="needle in haystack"), NarrowParameter(operator="search", operand="needle in haystack"),
] ]
self.assertTrue(ok_to_include_history(narrow, user_profile, False)) self.assertTrue(ok_to_include_history(narrow, user_profile, False))
@@ -1151,14 +1178,14 @@ class IncludeHistoryTest(ZulipTestCase):
# channels:public searches should not include history for guest members. # channels:public searches should not include history for guest members.
narrow = [ narrow = [
dict(operator="channels", operand="public"), NarrowParameter(operator="channels", operand="public"),
] ]
self.assertFalse(ok_to_include_history(narrow, guest_user_profile, False)) self.assertFalse(ok_to_include_history(narrow, guest_user_profile, False))
# Guest user can't access public channel # Guest user can't access public channel
self.subscribe(subscribed_user_profile, "public_channel_2") self.subscribe(subscribed_user_profile, "public_channel_2")
narrow = [ narrow = [
dict(operator="channel", operand="public_channel_2"), NarrowParameter(operator="channel", operand="public_channel_2"),
] ]
self.assertFalse(ok_to_include_history(narrow, guest_user_profile, False)) self.assertFalse(ok_to_include_history(narrow, guest_user_profile, False))
self.assertTrue(ok_to_include_history(narrow, subscribed_user_profile, False)) self.assertTrue(ok_to_include_history(narrow, subscribed_user_profile, False))
@@ -1166,7 +1193,7 @@ class IncludeHistoryTest(ZulipTestCase):
# Definitely, a guest user can't access the unsubscribed private channel # Definitely, a guest user can't access the unsubscribed private channel
self.subscribe(subscribed_user_profile, "private_channel_3") self.subscribe(subscribed_user_profile, "private_channel_3")
narrow = [ narrow = [
dict(operator="channel", operand="private_channel_3"), NarrowParameter(operator="channel", operand="private_channel_3"),
] ]
self.assertFalse(ok_to_include_history(narrow, guest_user_profile, False)) self.assertFalse(ok_to_include_history(narrow, guest_user_profile, False))
self.assertTrue(ok_to_include_history(narrow, subscribed_user_profile, False)) self.assertTrue(ok_to_include_history(narrow, subscribed_user_profile, False))
@@ -1175,7 +1202,7 @@ class IncludeHistoryTest(ZulipTestCase):
self.subscribe(guest_user_profile, "private_channel_4") self.subscribe(guest_user_profile, "private_channel_4")
self.subscribe(subscribed_user_profile, "private_channel_4") self.subscribe(subscribed_user_profile, "private_channel_4")
narrow = [ narrow = [
dict(operator="channel", operand="private_channel_4"), NarrowParameter(operator="channel", operand="private_channel_4"),
] ]
self.assertTrue(ok_to_include_history(narrow, guest_user_profile, False)) self.assertTrue(ok_to_include_history(narrow, guest_user_profile, False))
self.assertTrue(ok_to_include_history(narrow, subscribed_user_profile, False)) self.assertTrue(ok_to_include_history(narrow, subscribed_user_profile, False))
@@ -4123,8 +4150,8 @@ class GetOldMessagesTest(ZulipTestCase):
# If nothing relevant is muted, then exclude_muting_conditions() # If nothing relevant is muted, then exclude_muting_conditions()
# should return an empty list. # should return an empty list.
narrow: List[Dict[str, object]] = [ narrow: List[NarrowParameter] = [
dict(operator="channel", operand="Scotland"), NarrowParameter(operator="channel", operand="Scotland"),
] ]
muting_conditions = exclude_muting_conditions(user_profile, narrow) muting_conditions = exclude_muting_conditions(user_profile, narrow)
self.assertEqual(muting_conditions, []) self.assertEqual(muting_conditions, [])
@@ -4132,7 +4159,7 @@ class GetOldMessagesTest(ZulipTestCase):
# Also test that passing channel ID works # Also test that passing channel ID works
channel_id = get_stream("Scotland", realm).id channel_id = get_stream("Scotland", realm).id
narrow = [ narrow = [
dict(operator="channel", operand=channel_id), NarrowParameter(operator="channel", operand=channel_id),
] ]
muting_conditions = exclude_muting_conditions(user_profile, narrow) muting_conditions = exclude_muting_conditions(user_profile, narrow)
self.assertEqual(muting_conditions, []) self.assertEqual(muting_conditions, [])
@@ -4146,7 +4173,7 @@ class GetOldMessagesTest(ZulipTestCase):
# And verify that our query will exclude them. # And verify that our query will exclude them.
narrow = [ narrow = [
dict(operator="channel", operand="Scotland"), NarrowParameter(operator="channel", operand="Scotland"),
] ]
muting_conditions = exclude_muting_conditions(user_profile, narrow) muting_conditions = exclude_muting_conditions(user_profile, narrow)
@@ -4173,7 +4200,7 @@ WHERE NOT (recipient_id = %(recipient_id_1)s AND upper(subject) = upper(%(param_
# Using a bogus channel name should be similar to using no narrow at # Using a bogus channel name should be similar to using no narrow at
# all, and we'll exclude all mutes. # all, and we'll exclude all mutes.
narrow = [ narrow = [
dict(operator="channel", operand="bogus-channel-name"), NarrowParameter(operator="channel", operand="bogus-channel-name"),
] ]
muting_conditions = exclude_muting_conditions(user_profile, narrow) muting_conditions = exclude_muting_conditions(user_profile, narrow)

View File

@@ -16,7 +16,6 @@ from zerver.lib.exceptions import JsonableError, MissingAuthenticationError
from zerver.lib.message import get_first_visible_message_id, messages_for_ids from zerver.lib.message import get_first_visible_message_id, messages_for_ids
from zerver.lib.narrow import ( from zerver.lib.narrow import (
NarrowParameter, NarrowParameter,
OptionalNarrowListT,
add_narrow_conditions, add_narrow_conditions,
fetch_messages, fetch_messages,
is_spectator_compatible, is_spectator_compatible,
@@ -83,7 +82,9 @@ def get_search_fields(
} }
def clean_narrow_for_web_public_api(narrow: OptionalNarrowListT) -> OptionalNarrowListT: def clean_narrow_for_web_public_api(
narrow: Optional[List[NarrowParameter]],
) -> Optional[List[NarrowParameter]]:
if narrow is None: if narrow is None:
return None return None
@@ -93,7 +94,7 @@ def clean_narrow_for_web_public_api(narrow: OptionalNarrowListT) -> OptionalNarr
return [ return [
term term
for term in narrow for term in narrow
if not (term["operator"] == "in" and term["operand"] == "home" and not term["negated"]) if not (term.operator == "in" and term.operand == "home" and not term.negated)
] ]
@@ -123,11 +124,6 @@ def get_messages_backend(
if num_before > 0 and num_after > 0 and not include_anchor: if num_before > 0 and num_after > 0 and not include_anchor:
raise JsonableError(_("The anchor can only be excluded at an end of the range")) raise JsonableError(_("The anchor can only be excluded at an end of the range"))
if narrow is not None and len(narrow) > 0:
narrow_parameter_list: OptionalNarrowListT = [x.model_dump() for x in narrow]
else:
narrow_parameter_list = None
realm = get_valid_realm_from_request(request) realm = get_valid_realm_from_request(request)
if not maybe_user_profile.is_authenticated: if not maybe_user_profile.is_authenticated:
# If user is not authenticated, clients must include # If user is not authenticated, clients must include
@@ -142,11 +138,11 @@ def get_messages_backend(
# non-web-public stream messages) via this path. # non-web-public stream messages) via this path.
if not realm.allow_web_public_streams_access(): if not realm.allow_web_public_streams_access():
raise MissingAuthenticationError raise MissingAuthenticationError
narrow_parameter_list = clean_narrow_for_web_public_api(narrow_parameter_list) narrow = clean_narrow_for_web_public_api(narrow)
if not is_web_public_narrow(narrow_parameter_list): if not is_web_public_narrow(narrow):
raise MissingAuthenticationError raise MissingAuthenticationError
assert narrow_parameter_list is not None assert narrow is not None
if not is_spectator_compatible(narrow_parameter_list): if not is_spectator_compatible(narrow):
raise MissingAuthenticationError raise MissingAuthenticationError
# We use None to indicate unauthenticated requests as it's more # We use None to indicate unauthenticated requests as it's more
@@ -168,14 +164,14 @@ def get_messages_backend(
# email_address_visibility setting. # email_address_visibility setting.
client_gravatar = False client_gravatar = False
if narrow_parameter_list is not None: if narrow is not None:
# Add some metadata to our logging data for narrows # Add some metadata to our logging data for narrows
verbose_operators = [] verbose_operators = []
for term in narrow_parameter_list: for term in narrow:
if term["operator"] == "is": if term.operator == "is":
verbose_operators.append("is:" + term["operand"]) verbose_operators.append("is:" + term.operand)
else: else:
verbose_operators.append(term["operator"]) verbose_operators.append(term.operator)
log_data = RequestNotes.get_notes(request).log_data log_data = RequestNotes.get_notes(request).log_data
assert log_data is not None assert log_data is not None
log_data["extra"] = "[{}]".format(",".join(verbose_operators)) log_data["extra"] = "[{}]".format(",".join(verbose_operators))
@@ -206,7 +202,7 @@ def get_messages_backend(
cursor.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY") cursor.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY")
query_info = fetch_messages( query_info = fetch_messages(
narrow=narrow_parameter_list, narrow=narrow,
user_profile=user_profile, user_profile=user_profile,
realm=realm, realm=realm,
is_web_public_query=is_web_public_query, is_web_public_query=is_web_public_query,
@@ -297,8 +293,6 @@ def messages_in_narrow_backend(
msg_ids: Json[List[int]], msg_ids: Json[List[int]],
narrow: Json[List[NarrowParameter]], narrow: Json[List[NarrowParameter]],
) -> HttpResponse: ) -> HttpResponse:
narrow_parameter_list: OptionalNarrowListT = [x.model_dump() for x in narrow]
first_visible_message_id = get_first_visible_message_id(user_profile.realm) first_visible_message_id = get_first_visible_message_id(user_profile.realm)
msg_ids = [message_id for message_id in msg_ids if message_id >= first_visible_message_id] msg_ids = [message_id for message_id in msg_ids if message_id >= first_visible_message_id]
# This query is limited to messages the user has access to because they # This query is limited to messages the user has access to because they
@@ -326,7 +320,7 @@ def messages_in_narrow_backend(
user_profile=user_profile, user_profile=user_profile,
inner_msg_id_col=inner_msg_id_col, inner_msg_id_col=inner_msg_id_col,
query=query, query=query,
narrow=narrow_parameter_list, narrow=narrow,
is_web_public_query=False, is_web_public_query=False,
realm=user_profile.realm, realm=user_profile.realm,
) )

View File

@@ -92,13 +92,8 @@ def update_message_flags_for_narrow(
) )
num_after = min(num_after, MAX_MESSAGES_PER_UPDATE - num_before) num_after = min(num_after, MAX_MESSAGES_PER_UPDATE - num_before)
if narrow is not None and len(narrow) > 0:
narrow_dict = [x.model_dump() for x in narrow]
else:
narrow_dict = None
query_info = fetch_messages( query_info = fetch_messages(
narrow=narrow_dict, narrow=narrow,
user_profile=user_profile, user_profile=user_profile,
realm=user_profile.realm, realm=user_profile.realm,
is_web_public_query=False, is_web_public_query=False,