2016-04-24 17:08:51 +02:00
# -*- coding: utf-8 -*-
2016-12-08 09:39:48 +01:00
2016-06-21 21:05:44 +02:00
from __future__ import absolute_import
from __future__ import print_function
2016-07-17 04:07:07 +02:00
from django . db import connection
2016-04-24 17:08:51 +02:00
from django . test import override_settings
2016-06-21 21:05:44 +02:00
from sqlalchemy . sql import (
2017-02-22 22:13:57 +01:00
and_ , select , column , table ,
2016-06-21 21:05:44 +02:00
)
2016-12-08 09:39:48 +01:00
from sqlalchemy . sql import compiler # type: ignore
2016-06-21 21:05:44 +02:00
from zerver . models import (
Realm , Recipient , Stream , Subscription , UserProfile , Attachment ,
2017-01-04 05:30:48 +01:00
get_display_recipient , get_recipient , get_realm , get_stream , get_user_profile_by_email ,
2016-12-06 07:19:34 +01:00
Reaction
2016-06-21 21:05:44 +02:00
)
2016-10-04 15:52:26 +02:00
from zerver . lib . message import (
MessageDict ,
)
2016-07-16 22:56:33 +02:00
from zerver . lib . narrow import (
build_narrow_filter ,
)
2017-03-05 08:56:57 +01:00
from zerver . lib . request import JsonableError
2016-12-08 09:39:48 +01:00
from zerver . lib . str_utils import force_bytes
2016-07-19 08:12:35 +02:00
from zerver . lib . sqlalchemy_utils import get_sqlalchemy_connection
2016-06-21 21:05:44 +02:00
from zerver . lib . test_helpers import (
2016-11-10 19:30:09 +01:00
POSTRequestMock ,
2016-07-16 22:56:33 +02:00
TestCase ,
2017-03-19 04:40:28 +01:00
get_user_messages , queries_captured ,
2016-06-21 21:05:44 +02:00
)
2016-11-10 19:30:09 +01:00
from zerver . lib . test_classes import (
ZulipTestCase ,
)
2016-06-21 21:05:44 +02:00
from zerver . views . messages import (
2016-07-19 08:12:35 +02:00
exclude_muting_conditions ,
2017-03-24 07:51:46 +01:00
get_messages_backend , ok_to_include_history ,
2017-02-23 05:50:15 +01:00
NarrowBuilder , BadNarrowOperator , Query ,
LARGER_THAN_MAX_MESSAGE_ID ,
2016-06-21 21:05:44 +02:00
)
2017-03-03 19:01:52 +01:00
from typing import Dict , List , Mapping , Sequence , Tuple , Generic , Union , Any , Text
2016-06-21 21:05:44 +02:00
from six . moves import range
2016-07-16 22:56:33 +02:00
import os
2016-06-21 21:05:44 +02:00
import re
import ujson
def get_sqlalchemy_query_params ( query ) :
2016-12-08 09:39:48 +01:00
# type: (Text) -> Dict[Text, Text]
dialect = get_sqlalchemy_connection ( ) . dialect # type: ignore
2016-06-21 21:05:44 +02:00
comp = compiler . SQLCompiler ( dialect , query )
return comp . params
def fix_ws ( s ) :
2016-12-08 09:39:48 +01:00
# type: (Text) -> Text
2016-06-21 21:05:44 +02:00
return re . sub ( ' \ s+ ' , ' ' , str ( s ) ) . strip ( )
def get_recipient_id_for_stream_name ( realm , stream_name ) :
2016-12-08 09:39:48 +01:00
# type: (Realm, Text) -> Text
2016-06-21 21:05:44 +02:00
stream = get_stream ( stream_name , realm )
return get_recipient ( Recipient . STREAM , stream . id ) . id
def mute_stream ( realm , user_profile , stream_name ) :
2016-12-08 09:39:48 +01:00
# type: (Realm, Text, Text) -> None
2017-01-13 15:50:17 +01:00
stream = get_stream ( stream_name , realm )
2016-06-21 21:05:44 +02:00
recipient = Recipient . objects . get ( type_id = stream . id , type = Recipient . STREAM )
subscription = Subscription . objects . get ( recipient = recipient , user_profile = user_profile )
subscription . in_home_view = False
subscription . save ( )
2016-08-23 02:08:42 +02:00
class NarrowBuilderTest ( ZulipTestCase ) :
2016-06-21 21:05:44 +02:00
def setUp ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2017-01-04 05:30:48 +01:00
self . realm = get_realm ( ' zulip ' )
2016-06-21 21:05:44 +02:00
self . user_profile = get_user_profile_by_email ( " hamlet@zulip.com " )
self . builder = NarrowBuilder ( self . user_profile , column ( ' id ' ) )
2017-02-22 22:13:57 +01:00
self . raw_query = select ( [ column ( " id " ) ] , None , table ( " zerver_message " ) )
2016-06-21 21:05:44 +02:00
def test_add_term_using_not_defined_operator ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' not-defined ' , operand = ' any ' )
self . assertRaises ( BadNarrowOperator , self . _build_query , term )
def test_add_term_using_stream_operator ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' stream ' , operand = ' Scotland ' )
self . _do_add_term_test ( term , ' WHERE recipient_id = :recipient_id_1 ' )
def test_add_term_using_stream_operator_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' stream ' , operand = ' Scotland ' , negated = True )
self . _do_add_term_test ( term , ' WHERE recipient_id != :recipient_id_1 ' )
def test_add_term_using_stream_operator_and_non_existing_operand_should_raise_error ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' stream ' , operand = ' NonExistingStream ' )
self . assertRaises ( BadNarrowOperator , self . _build_query , term )
def test_add_term_using_is_operator_and_private_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' is ' , operand = ' private ' )
self . _do_add_term_test ( term , ' WHERE type = :type_1 OR type = :type_2 ' )
def test_add_term_using_is_operator_private_operand_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' is ' , operand = ' private ' , negated = True )
self . _do_add_term_test ( term , ' WHERE NOT (type = :type_1 OR type = :type_2) ' )
def test_add_term_using_is_operator_and_non_private_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
for operand in [ ' starred ' , ' mentioned ' , ' alerted ' ] :
term = dict ( operator = ' is ' , operand = operand )
self . _do_add_term_test ( term , ' WHERE (flags & :flags_1) != :param_1 ' )
def test_add_term_using_is_operator_non_private_operand_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
for operand in [ ' starred ' , ' mentioned ' , ' alerted ' ] :
term = dict ( operator = ' is ' , operand = operand , negated = True )
self . _do_add_term_test ( term , ' WHERE (flags & :flags_1) = :param_1 ' )
def test_add_term_using_non_supported_operator_should_raise_error ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' is ' , operand = ' non_supported ' )
self . assertRaises ( BadNarrowOperator , self . _build_query , term )
def test_add_term_using_topic_operator_and_lunch_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' topic ' , operand = ' lunch ' )
self . _do_add_term_test ( term , ' WHERE upper(subject) = upper(:param_1) ' )
def test_add_term_using_topic_operator_lunch_operand_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' topic ' , operand = ' lunch ' , negated = True )
self . _do_add_term_test ( term , ' WHERE upper(subject) != upper(:param_1) ' )
def test_add_term_using_topic_operator_and_personal_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' topic ' , operand = ' personal ' )
self . _do_add_term_test ( term , ' WHERE upper(subject) = upper(:param_1) ' )
def test_add_term_using_topic_operator_personal_operand_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' topic ' , operand = ' personal ' , negated = True )
self . _do_add_term_test ( term , ' WHERE upper(subject) != upper(:param_1) ' )
def test_add_term_using_sender_operator ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' sender ' , operand = ' othello@zulip.com ' )
self . _do_add_term_test ( term , ' WHERE sender_id = :param_1 ' )
def test_add_term_using_sender_operator_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' sender ' , operand = ' othello@zulip.com ' , negated = True )
self . _do_add_term_test ( term , ' WHERE sender_id != :param_1 ' )
def test_add_term_using_sender_operator_with_non_existing_user_as_operand ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' sender ' , operand = ' non-existing@zulip.com ' )
self . assertRaises ( BadNarrowOperator , self . _build_query , term )
def test_add_term_using_pm_with_operator_and_not_the_same_user_as_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' pm-with ' , operand = ' othello@zulip.com ' )
self . _do_add_term_test ( term , ' WHERE sender_id = :sender_id_1 AND recipient_id = :recipient_id_1 OR sender_id = :sender_id_2 AND recipient_id = :recipient_id_2 ' )
def test_add_term_using_pm_with_operator_not_the_same_user_as_operand_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' pm-with ' , operand = ' othello@zulip.com ' , negated = True )
self . _do_add_term_test ( term , ' WHERE NOT (sender_id = :sender_id_1 AND recipient_id = :recipient_id_1 OR sender_id = :sender_id_2 AND recipient_id = :recipient_id_2) ' )
def test_add_term_using_pm_with_operator_the_same_user_as_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' pm-with ' , operand = ' hamlet@zulip.com ' )
self . _do_add_term_test ( term , ' WHERE sender_id = :sender_id_1 AND recipient_id = :recipient_id_1 ' )
def test_add_term_using_pm_with_operator_the_same_user_as_operand_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' pm-with ' , operand = ' hamlet@zulip.com ' , negated = True )
self . _do_add_term_test ( term , ' WHERE NOT (sender_id = :sender_id_1 AND recipient_id = :recipient_id_1) ' )
def test_add_term_using_pm_with_operator_and_more_than_user_as_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' pm-with ' , operand = ' hamlet@zulip.com, othello@zulip.com ' )
self . _do_add_term_test ( term , ' WHERE recipient_id = :recipient_id_1 ' )
def test_add_term_using_pm_with_operator_more_than_user_as_operand_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' pm-with ' , operand = ' hamlet@zulip.com, othello@zulip.com ' , negated = True )
self . _do_add_term_test ( term , ' WHERE recipient_id != :recipient_id_1 ' )
def test_add_term_using_pm_with_operator_with_non_existing_user_as_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' pm-with ' , operand = ' non-existing@zulip.com ' )
self . assertRaises ( BadNarrowOperator , self . _build_query , term )
def test_add_term_using_pm_with_operator_with_existing_and_non_existing_user_as_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' pm-with ' , operand = ' othello@zulip.com,non-existing@zulip.com ' )
self . assertRaises ( BadNarrowOperator , self . _build_query , term )
def test_add_term_using_id_operator ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' id ' , operand = 555 )
self . _do_add_term_test ( term , ' WHERE id = :param_1 ' )
def test_add_term_using_id_operator_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' id ' , operand = 555 , negated = True )
self . _do_add_term_test ( term , ' WHERE id != :param_1 ' )
2017-03-23 23:35:37 +01:00
def test_add_term_using_group_pm_operator_and_not_the_same_user_as_operand ( self ) :
# type: () -> None
term = dict ( operator = ' group-pm-with ' , operand = ' othello@zulip.com ' )
self . _do_add_term_test ( term , ' WHERE recipient_id != recipient_id ' )
def test_add_term_using_group_pm_operator_not_the_same_user_as_operand_and_negated ( self ) : # NEGATED
# type: () -> None
term = dict ( operator = ' group-pm-with ' , operand = ' othello@zulip.com ' , negated = True )
self . _do_add_term_test ( term , ' WHERE recipient_id = recipient_id ' )
def test_add_term_using_group_pm_operator_with_non_existing_user_as_operand ( self ) :
# type: () -> None
term = dict ( operator = ' group-pm-with ' , operand = ' non-existing@zulip.com ' )
self . assertRaises ( BadNarrowOperator , self . _build_query , term )
2016-04-24 17:08:51 +02:00
@override_settings ( USING_PGROONGA = False )
2016-06-21 21:05:44 +02:00
def test_add_term_using_search_operator ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' search ' , operand = ' " french fries " ' )
self . _do_add_term_test ( term , ' WHERE (lower(content) LIKE lower(:content_1) OR lower(subject) LIKE lower(:subject_1)) AND (search_tsvector @@ plainto_tsquery(:param_2, :param_3)) ' )
2016-04-24 17:08:51 +02:00
@override_settings ( USING_PGROONGA = False )
2016-06-21 21:05:44 +02:00
def test_add_term_using_search_operator_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' search ' , operand = ' " french fries " ' , negated = True )
self . _do_add_term_test ( term , ' WHERE NOT (lower(content) LIKE lower(:content_1) OR lower(subject) LIKE lower(:subject_1)) AND NOT (search_tsvector @@ plainto_tsquery(:param_2, :param_3)) ' )
2016-04-24 17:08:51 +02:00
@override_settings ( USING_PGROONGA = True )
def test_add_term_using_search_operator_pgroonga ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-04-24 17:08:51 +02:00
term = dict ( operator = ' search ' , operand = ' " french fries " ' )
self . _do_add_term_test ( term , ' WHERE search_pgroonga @@ :search_pgroonga_1 ' )
@override_settings ( USING_PGROONGA = True )
def test_add_term_using_search_operator_and_negated_pgroonga ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-04-24 17:08:51 +02:00
term = dict ( operator = ' search ' , operand = ' " french fries " ' , negated = True )
self . _do_add_term_test ( term , ' WHERE NOT (search_pgroonga @@ :search_pgroonga_1) ' )
2016-06-21 21:05:44 +02:00
def test_add_term_using_has_operator_and_attachment_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' has ' , operand = ' attachment ' )
self . _do_add_term_test ( term , ' WHERE has_attachment ' )
def test_add_term_using_has_operator_attachment_operand_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' has ' , operand = ' attachment ' , negated = True )
self . _do_add_term_test ( term , ' WHERE NOT has_attachment ' )
def test_add_term_using_has_operator_and_image_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' has ' , operand = ' image ' )
self . _do_add_term_test ( term , ' WHERE has_image ' )
def test_add_term_using_has_operator_image_operand_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' has ' , operand = ' image ' , negated = True )
self . _do_add_term_test ( term , ' WHERE NOT has_image ' )
def test_add_term_using_has_operator_and_link_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' has ' , operand = ' link ' )
self . _do_add_term_test ( term , ' WHERE has_link ' )
def test_add_term_using_has_operator_link_operand_and_negated ( self ) : # NEGATED
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' has ' , operand = ' link ' , negated = True )
self . _do_add_term_test ( term , ' WHERE NOT has_link ' )
def test_add_term_using_has_operator_non_supported_operand_should_raise_error ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' has ' , operand = ' non_supported ' )
self . assertRaises ( BadNarrowOperator , self . _build_query , term )
def test_add_term_using_in_operator ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
mute_stream ( self . realm , self . user_profile , ' Verona ' )
term = dict ( operator = ' in ' , operand = ' home ' )
self . _do_add_term_test ( term , ' WHERE recipient_id NOT IN (:recipient_id_1) ' )
def test_add_term_using_in_operator_and_negated ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
# negated = True should not change anything
mute_stream ( self . realm , self . user_profile , ' Verona ' )
term = dict ( operator = ' in ' , operand = ' home ' , negated = True )
self . _do_add_term_test ( term , ' WHERE recipient_id NOT IN (:recipient_id_1) ' )
def test_add_term_using_in_operator_and_all_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
mute_stream ( self . realm , self . user_profile , ' Verona ' )
term = dict ( operator = ' in ' , operand = ' all ' )
query = self . _build_query ( term )
self . assertEqual ( str ( query ) , ' SELECT id \n FROM zerver_message ' )
def test_add_term_using_in_operator_all_operand_and_negated ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
# negated = True should not change anything
mute_stream ( self . realm , self . user_profile , ' Verona ' )
term = dict ( operator = ' in ' , operand = ' all ' , negated = True )
query = self . _build_query ( term )
self . assertEqual ( str ( query ) , ' SELECT id \n FROM zerver_message ' )
def test_add_term_using_in_operator_and_not_defined_operand ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' in ' , operand = ' not_defined ' )
self . assertRaises ( BadNarrowOperator , self . _build_query , term )
def test_add_term_using_near_operator ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
term = dict ( operator = ' near ' , operand = ' operand ' )
query = self . _build_query ( term )
self . assertEqual ( str ( query ) , ' SELECT id \n FROM zerver_message ' )
def _do_add_term_test ( self , term , where_clause ) :
2016-12-08 09:39:48 +01:00
# type: (Dict[str, Any], Text) -> None
2016-06-21 21:05:44 +02:00
self . assertTrue ( where_clause in str ( self . _build_query ( term ) ) )
def _build_query ( self , term ) :
2016-12-08 09:39:48 +01:00
# type: (Dict[str, Any]) -> Query
2016-06-21 21:05:44 +02:00
return self . builder . add_term ( self . raw_query , term )
2016-07-16 22:56:33 +02:00
class BuildNarrowFilterTest ( TestCase ) :
def test_build_narrow_filter ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-07-16 22:56:33 +02:00
fixtures_path = os . path . join ( os . path . dirname ( __file__ ) ,
' ../fixtures/narrow.json ' )
scenarios = ujson . loads ( open ( fixtures_path , ' r ' ) . read ( ) )
self . assertTrue ( len ( scenarios ) == 8 )
for scenario in scenarios :
narrow = scenario [ ' narrow ' ]
accept_events = scenario [ ' accept_events ' ]
reject_events = scenario [ ' reject_events ' ]
narrow_filter = build_narrow_filter ( narrow )
for e in accept_events :
self . assertTrue ( narrow_filter ( e ) )
for e in reject_events :
self . assertFalse ( narrow_filter ( e ) )
2017-03-05 08:56:57 +01:00
def test_build_narrow_filter_invalid ( self ) :
# type: () -> None
with self . assertRaises ( JsonableError ) :
build_narrow_filter ( [ " invalid_operator " , " operand " ] )
2016-08-23 02:08:42 +02:00
class IncludeHistoryTest ( ZulipTestCase ) :
2016-06-21 21:05:44 +02:00
def test_ok_to_include_history ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2017-01-04 05:30:48 +01:00
realm = get_realm ( ' zulip ' )
2016-10-21 22:59:59 +02:00
self . make_stream ( ' public_stream ' , realm = realm )
2016-06-21 21:05:44 +02:00
# Negated stream searches should not include history.
narrow = [
dict ( operator = ' stream ' , operand = ' public_stream ' , negated = True ) ,
]
self . assertFalse ( ok_to_include_history ( narrow , realm ) )
# Definitely forbid seeing history on private streams.
narrow = [
dict ( operator = ' stream ' , operand = ' private_stream ' ) ,
]
self . assertFalse ( ok_to_include_history ( narrow , realm ) )
# History doesn't apply to PMs.
narrow = [
dict ( operator = ' is ' , operand = ' private ' ) ,
]
self . assertFalse ( ok_to_include_history ( narrow , realm ) )
# If we are looking for something like starred messages, there is
# no point in searching historical messages.
narrow = [
dict ( operator = ' stream ' , operand = ' public_stream ' ) ,
dict ( operator = ' is ' , operand = ' starred ' ) ,
]
self . assertFalse ( ok_to_include_history ( narrow , realm ) )
# simple True case
narrow = [
dict ( operator = ' stream ' , operand = ' public_stream ' ) ,
]
self . assertTrue ( ok_to_include_history ( narrow , realm ) )
narrow = [
dict ( operator = ' stream ' , operand = ' public_stream ' ) ,
dict ( operator = ' topic ' , operand = ' whatever ' ) ,
dict ( operator = ' search ' , operand = ' needle in haystack ' ) ,
]
self . assertTrue ( ok_to_include_history ( narrow , realm ) )
2016-08-23 02:08:42 +02:00
class GetOldMessagesTest ( ZulipTestCase ) :
2016-06-21 21:05:44 +02:00
2016-07-24 16:50:30 +02:00
def get_and_check_messages ( self , modified_params ) :
2016-12-08 09:39:48 +01:00
# type: (Dict[str, Union[str, int]]) -> Dict[str, Dict]
post_params = { " anchor " : 1 , " num_before " : 1 , " num_after " : 1 } # type: Dict[str, Union[str, int]]
2016-06-21 21:05:44 +02:00
post_params . update ( modified_params )
2016-07-28 00:38:45 +02:00
payload = self . client_get ( " /json/messages " , dict ( post_params ) )
2016-07-24 16:50:30 +02:00
self . assert_json_success ( payload )
result = ujson . loads ( payload . content )
2016-06-21 21:05:44 +02:00
self . assertIn ( " messages " , result )
self . assertIsInstance ( result [ " messages " ] , list )
for message in result [ " messages " ] :
for field in ( " content " , " content_type " , " display_recipient " ,
" avatar_url " , " recipient_id " , " sender_full_name " ,
2016-12-06 07:19:34 +01:00
" sender_short_name " , " timestamp " , " reactions " ) :
2016-06-21 21:05:44 +02:00
self . assertIn ( field , message )
# TODO: deprecate soon in favor of avatar_url
self . assertIn ( ' gravatar_hash ' , message )
2016-07-24 16:50:30 +02:00
return result
2016-06-21 21:05:44 +02:00
def get_query_ids ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> Dict[Text, int]
2016-06-21 21:05:44 +02:00
hamlet_user = get_user_profile_by_email ( ' hamlet@zulip.com ' )
othello_user = get_user_profile_by_email ( ' othello@zulip.com ' )
2016-12-08 09:39:48 +01:00
query_ids = { } # type: Dict[Text, int]
2016-06-21 21:05:44 +02:00
scotland_stream = get_stream ( ' Scotland ' , hamlet_user . realm )
query_ids [ ' scotland_recipient ' ] = get_recipient ( Recipient . STREAM , scotland_stream . id ) . id
query_ids [ ' hamlet_id ' ] = hamlet_user . id
query_ids [ ' othello_id ' ] = othello_user . id
query_ids [ ' hamlet_recipient ' ] = get_recipient ( Recipient . PERSONAL , hamlet_user . id ) . id
query_ids [ ' othello_recipient ' ] = get_recipient ( Recipient . PERSONAL , othello_user . id ) . id
return query_ids
2017-03-24 07:51:46 +01:00
def test_successful_get_messages_reaction ( self ) :
2016-12-06 07:19:34 +01:00
# type: () -> None
"""
Test old ` / json / messages ` returns reactions .
"""
self . login ( " hamlet@zulip.com " )
messages = self . get_and_check_messages ( dict ( ) )
message_id = messages [ ' messages ' ] [ 0 ] [ ' id ' ]
self . login ( " othello@zulip.com " )
2017-05-01 01:27:41 +02:00
reaction_name = ' slightly_smiling_face '
2016-12-06 07:19:34 +01:00
url = ' /json/messages/ {} /emoji_reactions/ {} ' . format ( message_id , reaction_name )
payload = self . client_put ( url )
self . assert_json_success ( payload )
self . login ( " hamlet@zulip.com " )
messages = self . get_and_check_messages ( { } )
message_to_assert = None
for message in messages [ ' messages ' ] :
if message [ ' id ' ] == message_id :
message_to_assert = message
break
2016-12-16 02:01:34 +01:00
self . assertEqual ( len ( message_to_assert [ ' reactions ' ] ) , 1 )
self . assertEqual ( message_to_assert [ ' reactions ' ] [ 0 ] [ ' emoji_name ' ] ,
2016-12-16 05:51:27 +01:00
reaction_name )
2016-12-06 07:19:34 +01:00
2017-03-24 07:51:46 +01:00
def test_successful_get_messages ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
A call to GET / json / messages with valid parameters returns a list of
messages .
"""
self . login ( " hamlet@zulip.com " )
2016-07-24 16:50:30 +02:00
self . get_and_check_messages ( dict ( ) )
2016-06-21 21:05:44 +02:00
# We have to support the legacy tuple style while there are old
# clients around, which might include third party home-grown bots.
2016-12-08 09:39:48 +01:00
self . get_and_check_messages ( dict ( narrow = ujson . dumps ( [ [ ' pm-with ' , ' othello@zulip.com ' ] ] ) ) )
2016-06-21 21:05:44 +02:00
2016-12-08 09:39:48 +01:00
self . get_and_check_messages ( dict ( narrow = ujson . dumps ( [ dict ( operator = ' pm-with ' , operand = ' othello@zulip.com ' ) ] ) ) )
2016-06-21 21:05:44 +02:00
2017-03-24 07:51:46 +01:00
def test_get_messages_with_narrow_pm_with ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
A request for old messages with a narrow by pm - with only returns
conversations with that user .
"""
me = ' hamlet@zulip.com '
2016-11-29 07:22:02 +01:00
2016-06-21 21:05:44 +02:00
def dr_emails ( dr ) :
2016-12-08 09:39:48 +01:00
# type: (Union[Text, List[Dict[str, Any]]]) -> Text
assert isinstance ( dr , list )
2016-06-21 21:05:44 +02:00
return ' , ' . join ( sorted ( set ( [ r [ ' email ' ] for r in dr ] + [ me ] ) ) )
2017-03-14 09:15:37 +01:00
self . send_message ( me , ' iago@zulip.com ' , Recipient . PERSONAL )
self . send_message ( me ,
[ ' iago@zulip.com ' , ' cordelia@zulip.com ' ] ,
Recipient . HUDDLE )
2016-06-21 21:05:44 +02:00
personals = [ m for m in get_user_messages ( get_user_profile_by_email ( me ) )
2017-01-24 05:50:04 +01:00
if m . recipient . type == Recipient . PERSONAL or
m . recipient . type == Recipient . HUDDLE ]
2017-03-14 09:15:37 +01:00
for personal in personals :
emails = dr_emails ( get_display_recipient ( personal . recipient ) )
2016-06-21 21:05:44 +02:00
2017-03-14 09:15:37 +01:00
self . login ( me )
narrow = [ dict ( operator = ' pm-with ' , operand = emails ) ]
result = self . get_and_check_messages ( dict ( narrow = ujson . dumps ( narrow ) ) )
2016-06-21 21:05:44 +02:00
2017-03-14 09:15:37 +01:00
for message in result [ " messages " ] :
self . assertEqual ( dr_emails ( message [ ' display_recipient ' ] ) , emails )
2016-06-21 21:05:44 +02:00
2017-03-23 23:35:37 +01:00
def test_get_messages_with_narrow_group_pm_with ( self ) :
# type: () -> None
"""
A request for old messages with a narrow by group - pm - with only returns
group - private conversations with that user .
"""
me = ' hamlet@zulip.com '
matching_message_ids = [ ]
matching_message_ids . append ( self . send_message ( me , [ ' iago@zulip.com ' , ' cordelia@zulip.com ' , ' othello@zulip.com ' ] , Recipient . HUDDLE ) )
matching_message_ids . append ( self . send_message ( me , [ ' cordelia@zulip.com ' , ' othello@zulip.com ' ] , Recipient . HUDDLE ) )
non_matching_message_ids = [ ]
non_matching_message_ids . append ( self . send_message ( me , ' cordelia@zulip.com ' , Recipient . PERSONAL ) )
non_matching_message_ids . append ( self . send_message ( me , [ ' iago@zulip.com ' , ' othello@zulip.com ' ] , Recipient . HUDDLE ) )
non_matching_message_ids . append ( self . send_message ( ' cordelia@zulip.com ' , [ ' iago@zulip.com ' , ' othello@zulip.com ' ] , Recipient . HUDDLE ) )
self . login ( me )
narrow = [ dict ( operator = ' group-pm-with ' , operand = ' cordelia@zulip.com ' ) ]
result = self . get_and_check_messages ( dict ( narrow = ujson . dumps ( narrow ) ) )
for message in result [ " messages " ] :
self . assertIn ( message [ " id " ] , matching_message_ids )
self . assertNotIn ( message [ " id " ] , non_matching_message_ids )
2017-03-24 07:51:46 +01:00
def test_get_messages_with_narrow_stream ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
A request for old messages with a narrow by stream only returns
messages for that stream .
"""
self . login ( " hamlet@zulip.com " )
2017-02-22 21:23:22 +01:00
# We need to subscribe to a stream and then send a message to
2016-06-21 21:05:44 +02:00
# it to ensure that we actually have a stream message in this
# narrow view.
2016-10-20 00:21:34 +02:00
self . subscribe_to_stream ( " hamlet@zulip.com " , ' Scotland ' )
2016-06-21 21:05:44 +02:00
self . send_message ( " hamlet@zulip.com " , " Scotland " , Recipient . STREAM )
messages = get_user_messages ( get_user_profile_by_email ( " hamlet@zulip.com " ) )
stream_messages = [ msg for msg in messages if msg . recipient . type == Recipient . STREAM ]
stream_name = get_display_recipient ( stream_messages [ 0 ] . recipient )
stream_id = stream_messages [ 0 ] . recipient . id
narrow = [ dict ( operator = ' stream ' , operand = stream_name ) ]
2016-07-24 16:50:30 +02:00
result = self . get_and_check_messages ( dict ( narrow = ujson . dumps ( narrow ) ) )
2016-06-21 21:05:44 +02:00
for message in result [ " messages " ] :
self . assertEqual ( message [ " type " ] , " stream " )
self . assertEqual ( message [ " recipient_id " ] , stream_id )
2017-03-24 07:51:46 +01:00
def test_get_messages_with_narrow_stream_mit_unicode_regex ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
A request for old messages for a user in the mit . edu relam with unicode
stream name should be correctly escaped in the database query .
"""
self . login ( " starnine@mit.edu " )
# We need to susbcribe to a stream and then send a message to
# it to ensure that we actually have a stream message in this
# narrow view.
2016-10-21 22:59:59 +02:00
lambda_stream_name = u " \u03bb -stream "
self . subscribe_to_stream ( " starnine@mit.edu " , lambda_stream_name )
2016-06-21 21:05:44 +02:00
2016-10-21 22:59:59 +02:00
lambda_stream_d_name = u " \u03bb -stream.d "
self . subscribe_to_stream ( " starnine@mit.edu " , lambda_stream_d_name )
2016-06-21 21:05:44 +02:00
self . send_message ( " starnine@mit.edu " , u " \u03bb -stream " , Recipient . STREAM )
self . send_message ( " starnine@mit.edu " , u " \u03bb -stream.d " , Recipient . STREAM )
narrow = [ dict ( operator = ' stream ' , operand = u ' \u03bb -stream ' ) ]
2016-07-24 16:50:30 +02:00
result = self . get_and_check_messages ( dict ( num_after = 2 ,
narrow = ujson . dumps ( narrow ) ) )
2016-06-21 21:05:44 +02:00
messages = get_user_messages ( get_user_profile_by_email ( " starnine@mit.edu " ) )
stream_messages = [ msg for msg in messages if msg . recipient . type == Recipient . STREAM ]
self . assertEqual ( len ( result [ " messages " ] ) , 2 )
for i , message in enumerate ( result [ " messages " ] ) :
self . assertEqual ( message [ " type " ] , " stream " )
stream_id = stream_messages [ i ] . recipient . id
self . assertEqual ( message [ " recipient_id " ] , stream_id )
2017-03-24 07:51:46 +01:00
def test_get_messages_with_narrow_topic_mit_unicode_regex ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
2017-02-22 21:23:22 +01:00
A request for old messages for a user in the mit . edu realm with unicode
2016-06-21 21:05:44 +02:00
topic name should be correctly escaped in the database query .
"""
self . login ( " starnine@mit.edu " )
# We need to susbcribe to a stream and then send a message to
# it to ensure that we actually have a stream message in this
# narrow view.
2016-10-20 00:21:34 +02:00
self . subscribe_to_stream ( " starnine@mit.edu " , " Scotland " )
2016-06-21 21:05:44 +02:00
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u " \u03bb -topic " )
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u " \u03bb -topic.d " )
2017-02-22 21:23:22 +01:00
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u " \u03bb -topic.d.d " )
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u " \u03bb -topic.d.d.d " )
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u " \u03bb -topic.d.d.d.d " )
2016-06-21 21:05:44 +02:00
narrow = [ dict ( operator = ' topic ' , operand = u ' \u03bb -topic ' ) ]
2016-07-24 16:50:30 +02:00
result = self . get_and_check_messages ( dict (
2017-02-22 21:23:22 +01:00
num_after = 100 ,
2016-07-24 16:50:30 +02:00
narrow = ujson . dumps ( narrow ) ) )
2016-06-21 21:05:44 +02:00
messages = get_user_messages ( get_user_profile_by_email ( " starnine@mit.edu " ) )
stream_messages = [ msg for msg in messages if msg . recipient . type == Recipient . STREAM ]
2017-02-22 21:23:22 +01:00
self . assertEqual ( len ( result [ " messages " ] ) , 5 )
for i , message in enumerate ( result [ " messages " ] ) :
self . assertEqual ( message [ " type " ] , " stream " )
stream_id = stream_messages [ i ] . recipient . id
self . assertEqual ( message [ " recipient_id " ] , stream_id )
2017-03-24 07:51:46 +01:00
def test_get_messages_with_narrow_topic_mit_personal ( self ) :
2017-02-22 21:23:22 +01:00
# type: () -> None
"""
We handle . d grouping for MIT realm personal messages correctly .
"""
self . login ( " starnine@mit.edu " )
# We need to susbcribe to a stream and then send a message to
# it to ensure that we actually have a stream message in this
# narrow view.
self . subscribe_to_stream ( " starnine@mit.edu " , " Scotland " )
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u " .d.d " )
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u " PERSONAL " )
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u ' (instance " " ).d ' )
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u " .d.d.d " )
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u " personal.d " )
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u ' (instance " " ) ' )
self . send_message ( " starnine@mit.edu " , " Scotland " , Recipient . STREAM ,
subject = u " .d.d.d.d " )
narrow = [ dict ( operator = ' topic ' , operand = u ' personal.d.d ' ) ]
result = self . get_and_check_messages ( dict (
num_before = 50 ,
num_after = 50 ,
narrow = ujson . dumps ( narrow ) ) )
messages = get_user_messages ( get_user_profile_by_email ( " starnine@mit.edu " ) )
stream_messages = [ msg for msg in messages if msg . recipient . type == Recipient . STREAM ]
self . assertEqual ( len ( result [ " messages " ] ) , 7 )
2016-06-21 21:05:44 +02:00
for i , message in enumerate ( result [ " messages " ] ) :
self . assertEqual ( message [ " type " ] , " stream " )
stream_id = stream_messages [ i ] . recipient . id
self . assertEqual ( message [ " recipient_id " ] , stream_id )
2017-03-24 07:51:46 +01:00
def test_get_messages_with_narrow_sender ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
A request for old messages with a narrow by sender only returns
messages sent by that person .
"""
self . login ( " hamlet@zulip.com " )
# We need to send a message here to ensure that we actually
# have a stream message in this narrow view.
self . send_message ( " hamlet@zulip.com " , " Scotland " , Recipient . STREAM )
self . send_message ( " othello@zulip.com " , " Scotland " , Recipient . STREAM )
self . send_message ( " othello@zulip.com " , " hamlet@zulip.com " , Recipient . PERSONAL )
self . send_message ( " iago@zulip.com " , " Scotland " , Recipient . STREAM )
narrow = [ dict ( operator = ' sender ' , operand = ' othello@zulip.com ' ) ]
2016-07-24 16:50:30 +02:00
result = self . get_and_check_messages ( dict ( narrow = ujson . dumps ( narrow ) ) )
2016-06-21 21:05:44 +02:00
for message in result [ " messages " ] :
self . assertEqual ( message [ " sender_email " ] , " othello@zulip.com " )
2016-09-19 16:34:01 +02:00
def _update_tsvector_index ( self ) :
# type: () -> None
# We use brute force here and update our text search index
# for the entire zerver_message table (which is small in test
# mode). In production there is an async process which keeps
# the search index up to date.
with connection . cursor ( ) as cursor :
cursor . execute ( """
UPDATE zerver_message SET
search_tsvector = to_tsvector ( ' zulip.english_us_search ' ,
subject | | rendered_content )
""" )
2016-09-19 20:18:33 +02:00
@override_settings ( USING_PGROONGA = False )
def test_messages_in_narrow ( self ) :
# type: () -> None
email = ' cordelia@zulip.com '
self . login ( email )
def send ( content ) :
2016-12-08 09:39:48 +01:00
# type: (Text) -> int
2016-09-19 20:18:33 +02:00
msg_id = self . send_message (
sender_name = email ,
raw_recipients = " Verona " ,
message_type = Recipient . STREAM ,
content = content ,
)
return msg_id
good_id = send ( ' KEYWORDMATCH and should work ' )
bad_id = send ( ' no match ' )
msg_ids = [ good_id , bad_id ]
send ( ' KEYWORDMATCH but not in msg_ids ' )
self . _update_tsvector_index ( )
narrow = [
dict ( operator = ' search ' , operand = ' KEYWORDMATCH ' ) ,
]
raw_params = dict ( msg_ids = msg_ids , narrow = narrow )
params = { k : ujson . dumps ( v ) for k , v in raw_params . items ( ) }
result = self . client_post ( ' /json/messages_in_narrow ' , params )
self . assert_json_success ( result )
messages = ujson . loads ( result . content ) [ ' messages ' ]
self . assertEqual ( len ( list ( messages . keys ( ) ) ) , 1 )
message = messages [ str ( good_id ) ]
self . assertEqual ( message [ ' match_content ' ] ,
2016-12-03 00:04:17 +01:00
u ' <p><span class= " highlight " >KEYWORDMATCH</span> and should work</p> ' )
2016-09-19 20:18:33 +02:00
2016-04-24 17:08:51 +02:00
@override_settings ( USING_PGROONGA = False )
2017-03-24 07:51:46 +01:00
def test_get_messages_with_search ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-07-17 04:07:07 +02:00
self . login ( " cordelia@zulip.com " )
messages_to_search = [
( ' breakfast ' , ' there are muffins in the conference room ' ) ,
( ' lunch plans ' , ' I am hungry! ' ) ,
( ' meetings ' , ' discuss lunch after lunch ' ) ,
( ' meetings ' , ' please bring your laptops to take notes ' ) ,
( ' dinner ' , ' Anybody staying late tonight? ' ) ,
]
for topic , content in messages_to_search :
self . send_message (
sender_name = " cordelia@zulip.com " ,
raw_recipients = " Verona " ,
message_type = Recipient . STREAM ,
content = content ,
subject = topic ,
)
2016-09-19 16:34:01 +02:00
self . _update_tsvector_index ( )
2016-07-17 04:07:07 +02:00
narrow = [
dict ( operator = ' sender ' , operand = ' cordelia@zulip.com ' ) ,
dict ( operator = ' search ' , operand = ' lunch ' ) ,
]
2016-07-24 16:50:30 +02:00
result = self . get_and_check_messages ( dict (
2016-07-17 04:07:07 +02:00
narrow = ujson . dumps ( narrow ) ,
anchor = 0 ,
num_after = 10 ,
2016-12-08 09:39:48 +01:00
) ) # type: Dict[str, Dict]
2016-07-17 04:07:07 +02:00
self . assertEqual ( len ( result [ ' messages ' ] ) , 2 )
messages = result [ ' messages ' ]
meeting_message = [ m for m in messages if m [ ' subject ' ] == ' meetings ' ] [ 0 ]
self . assertEqual (
meeting_message [ ' match_subject ' ] ,
' meetings ' )
self . assertEqual (
meeting_message [ ' match_content ' ] ,
' <p>discuss <span class= " highlight " >lunch</span> after ' +
' <span class= " highlight " >lunch</span></p> ' )
meeting_message = [ m for m in messages if m [ ' subject ' ] == ' lunch plans ' ] [ 0 ]
self . assertEqual (
meeting_message [ ' match_subject ' ] ,
' <span class= " highlight " >lunch</span> plans ' )
self . assertEqual (
meeting_message [ ' match_content ' ] ,
' <p>I am hungry!</p> ' )
2017-01-16 16:53:20 +01:00
# Should not crash when multiple search operands are present
multi_search_narrow = [
dict ( operator = ' search ' , operand = ' discuss ' ) ,
dict ( operator = ' search ' , operand = ' after ' ) ,
]
multi_search_result = self . get_and_check_messages ( dict (
narrow = ujson . dumps ( multi_search_narrow ) ,
anchor = 0 ,
num_after = 10 ,
) ) # type: Dict[str, Dict]
self . assertEqual ( len ( multi_search_result [ ' messages ' ] ) , 1 )
self . assertEqual ( multi_search_result [ ' messages ' ] [ 0 ] [ ' match_content ' ] , ' <p><span class= " highlight " >discuss</span> lunch <span class= " highlight " >after</span> lunch</p> ' )
2017-02-23 00:21:26 +01:00
@override_settings ( USING_PGROONGA = False )
2017-03-24 07:51:46 +01:00
def test_get_messages_with_search_not_subscribed ( self ) :
2017-02-23 00:21:26 +01:00
# type: () -> None
""" Verify support for searching a stream you ' re not subscribed to """
self . subscribe_to_stream ( " hamlet@zulip.com " , " newstream " )
self . send_message (
sender_name = " hamlet@zulip.com " ,
raw_recipients = " newstream " ,
message_type = Recipient . STREAM ,
content = " Public special content! " ,
subject = " new " ,
)
self . _update_tsvector_index ( )
self . login ( " cordelia@zulip.com " )
stream_search_narrow = [
dict ( operator = ' search ' , operand = ' special ' ) ,
dict ( operator = ' stream ' , operand = ' newstream ' ) ,
]
stream_search_result = self . get_and_check_messages ( dict (
narrow = ujson . dumps ( stream_search_narrow ) ,
anchor = 0 ,
num_after = 10 ,
num_before = 10 ,
) ) # type: Dict[str, Dict]
self . assertEqual ( len ( stream_search_result [ ' messages ' ] ) , 1 )
self . assertEqual ( stream_search_result [ ' messages ' ] [ 0 ] [ ' match_content ' ] ,
' <p>Public <span class= " highlight " >special</span> content!</p> ' )
2016-04-24 17:08:51 +02:00
@override_settings ( USING_PGROONGA = True )
2017-03-24 07:51:46 +01:00
def test_get_messages_with_search_pgroonga ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-04-24 17:08:51 +02:00
self . login ( " cordelia@zulip.com " )
messages_to_search = [
( u ' 日本語 ' , u ' こんにちは。今日はいい天気ですね。 ' ) ,
( u ' 日本語 ' , u ' 今朝はごはんを食べました。 ' ) ,
( u ' 日本語 ' , u ' 昨日、日本のお菓子を送りました。 ' ) ,
( ' english ' , u ' I want to go to 日本! ' ) ,
2017-04-06 15:59:56 +02:00
( ' english ' , ' Can you speak https://en.wikipedia.org/wiki/Japanese? ' ) ,
2016-04-24 17:08:51 +02:00
]
for topic , content in messages_to_search :
self . send_message (
sender_name = " cordelia@zulip.com " ,
raw_recipients = " Verona " ,
message_type = Recipient . STREAM ,
content = content ,
subject = topic ,
)
# We use brute force here and update our text search index
# for the entire zerver_message table (which is small in test
# mode). In production there is an async process which keeps
# the search index up to date.
with connection . cursor ( ) as cursor :
cursor . execute ( """
UPDATE zerver_message SET
search_pgroonga = subject | | ' ' | | rendered_content
""" )
narrow = [
dict ( operator = ' search ' , operand = u ' 日本 ' ) ,
]
result = self . get_and_check_messages ( dict (
narrow = ujson . dumps ( narrow ) ,
anchor = 0 ,
num_after = 10 ,
2016-12-08 09:39:48 +01:00
) ) # type: Dict[str, Dict]
2016-04-24 17:08:51 +02:00
self . assertEqual ( len ( result [ ' messages ' ] ) , 4 )
messages = result [ ' messages ' ]
japanese_message = [ m for m in messages if m [ ' subject ' ] == u ' 日本語 ' ] [ - 1 ]
self . assertEqual (
japanese_message [ ' match_subject ' ] ,
u ' <span class= " highlight " >日本</span>語 ' )
self . assertEqual (
japanese_message [ ' match_content ' ] ,
u ' <p>昨日、<span class= " highlight " >日本</span>の ' +
u ' お菓子を送りました。</p> ' )
english_message = [ m for m in messages if m [ ' subject ' ] == ' english ' ] [ 0 ]
self . assertEqual (
english_message [ ' match_subject ' ] ,
' english ' )
2017-03-30 06:59:45 +02:00
self . assertIn (
2016-04-24 17:08:51 +02:00
english_message [ ' match_content ' ] ,
2017-03-29 12:02:16 +02:00
# NOTE: The whitespace here is off due to a pgroonga bug.
2017-03-30 06:59:45 +02:00
# This bug is a pgroonga regression and according to one of
# the author, this should be fixed in its next release.
[ u ' <p>I want to go to <span class= " highlight " >日本</span>!</p> ' , # This is correct.
u ' <p>I want to go to<span class= " highlight " > 日本</span>!</p> ' , ] )
2016-07-17 04:07:07 +02:00
2017-01-16 16:53:20 +01:00
# Should not crash when multiple search operands are present
multi_search_narrow = [
dict ( operator = ' search ' , operand = ' can ' ) ,
dict ( operator = ' search ' , operand = ' speak ' ) ,
2017-04-06 15:59:56 +02:00
dict ( operator = ' search ' , operand = ' wiki ' ) ,
2017-01-16 16:53:20 +01:00
]
multi_search_result = self . get_and_check_messages ( dict (
narrow = ujson . dumps ( multi_search_narrow ) ,
anchor = 0 ,
num_after = 10 ,
) ) # type: Dict[str, Dict]
self . assertEqual ( len ( multi_search_result [ ' messages ' ] ) , 1 )
2017-04-06 15:59:56 +02:00
self . assertEqual ( multi_search_result [ ' messages ' ] [ 0 ] [ ' match_content ' ] ,
' <p><span class= " highlight " >Can</span> you <span class= " highlight " >speak</span> <a href= " https://en.wikipedia.org/wiki/Japanese " target= " _blank " title= " https://en.wikipedia.org/wiki/Japanese " >https://en.<span class= " highlight " >wiki</span>pedia.org/<span class= " highlight " >wiki</span>/Japanese</a>?</p> ' )
2017-01-16 16:53:20 +01:00
2017-03-24 07:51:46 +01:00
def test_get_messages_with_only_searching_anchor ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
Test that specifying an anchor but 0 for num_before and num_after
returns at most 1 message .
"""
self . login ( " cordelia@zulip.com " )
anchor = self . send_message ( " cordelia@zulip.com " , " Verona " , Recipient . STREAM )
narrow = [ dict ( operator = ' sender ' , operand = ' cordelia@zulip.com ' ) ]
2016-07-24 16:50:30 +02:00
result = self . get_and_check_messages ( dict ( narrow = ujson . dumps ( narrow ) ,
anchor = anchor , num_before = 0 ,
2016-12-08 09:39:48 +01:00
num_after = 0 ) ) # type: Dict[str, Dict]
2016-06-21 21:05:44 +02:00
self . assertEqual ( len ( result [ ' messages ' ] ) , 1 )
narrow = [ dict ( operator = ' is ' , operand = ' mentioned ' ) ]
2016-07-24 16:50:30 +02:00
result = self . get_and_check_messages ( dict ( narrow = ujson . dumps ( narrow ) ,
anchor = anchor , num_before = 0 ,
num_after = 0 ) )
2016-06-21 21:05:44 +02:00
self . assertEqual ( len ( result [ ' messages ' ] ) , 0 )
def test_missing_params ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
anchor , num_before , and num_after are all required
2017-03-24 07:51:46 +01:00
POST parameters for get_messages .
2016-06-21 21:05:44 +02:00
"""
self . login ( " hamlet@zulip.com " )
2016-12-08 09:39:48 +01:00
required_args = ( ( " anchor " , 1 ) , ( " num_before " , 1 ) , ( " num_after " , 1 ) ) # type: Tuple[Tuple[Text, int], ...]
2016-06-21 21:05:44 +02:00
for i in range ( len ( required_args ) ) :
post_params = dict ( required_args [ : i ] + required_args [ i + 1 : ] )
2016-07-28 00:38:45 +02:00
result = self . client_get ( " /json/messages " , post_params )
2016-06-21 21:05:44 +02:00
self . assert_json_error ( result ,
" Missing ' %s ' argument " % ( required_args [ i ] [ 0 ] , ) )
def test_bad_int_params ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
num_before , num_after , and narrow must all be non - negative
integers or strings that can be converted to non - negative integers .
"""
self . login ( " hamlet@zulip.com " )
other_params = [ ( " narrow " , { } ) , ( " anchor " , 0 ) ]
int_params = [ " num_before " , " num_after " ]
bad_types = ( False , " " , " -1 " , - 1 )
for idx , param in enumerate ( int_params ) :
for type in bad_types :
# Rotate through every bad type for every integer
# parameter, one at a time.
2016-12-03 18:07:49 +01:00
post_params = dict ( other_params + [ ( param , type ) ] +
[ ( other_param , 0 ) for other_param in
2016-11-30 14:17:35 +01:00
int_params [ : idx ] + int_params [ idx + 1 : ] ]
2016-06-21 21:05:44 +02:00
)
2016-07-28 00:38:45 +02:00
result = self . client_get ( " /json/messages " , post_params )
2016-06-21 21:05:44 +02:00
self . assert_json_error ( result ,
" Bad value for ' %s ' : %s " % ( param , type ) )
def test_bad_narrow_type ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
narrow must be a list of string pairs .
"""
self . login ( " hamlet@zulip.com " )
2017-03-20 05:02:30 +01:00
other_params = [ ( " anchor " , 0 ) , ( " num_before " , 0 ) , ( " num_after " , 0 ) ] # type: List[Tuple[Text, Union[int, str, bool]]]
2016-06-21 21:05:44 +02:00
bad_types = ( False , 0 , ' ' , ' { malformed json, ' ,
2016-12-08 09:39:48 +01:00
' {foo: 3} ' , ' [1,2] ' , ' [[ " x " , " y " , " z " ]] ' ) # type: Tuple[Union[int, str, bool], ...]
2016-06-21 21:05:44 +02:00
for type in bad_types :
post_params = dict ( other_params + [ ( " narrow " , type ) ] )
2016-07-28 00:38:45 +02:00
result = self . client_get ( " /json/messages " , post_params )
2016-06-21 21:05:44 +02:00
self . assert_json_error ( result ,
" Bad value for ' narrow ' : %s " % ( type , ) )
def test_bad_narrow_operator ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
Unrecognized narrow operators are rejected .
"""
self . login ( " hamlet@zulip.com " )
for operator in [ ' ' , ' foo ' , ' stream:verona ' , ' __init__ ' ] :
narrow = [ dict ( operator = operator , operand = ' ' ) ]
params = dict ( anchor = 0 , num_before = 0 , num_after = 0 , narrow = ujson . dumps ( narrow ) )
2016-07-28 00:38:45 +02:00
result = self . client_get ( " /json/messages " , params )
2016-06-21 21:05:44 +02:00
self . assert_json_error_contains ( result ,
2016-12-03 00:04:17 +01:00
" Invalid narrow operator: unknown operator " )
2016-06-21 21:05:44 +02:00
2016-07-26 23:53:14 +02:00
def test_non_string_narrow_operand_in_dict ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-07-26 23:53:14 +02:00
"""
We expect search operands to be strings , not integers .
"""
self . login ( " hamlet@zulip.com " )
not_a_string = 42
narrow = [ dict ( operator = ' stream ' , operand = not_a_string ) ]
params = dict ( anchor = 0 , num_before = 0 , num_after = 0 , narrow = ujson . dumps ( narrow ) )
2016-07-28 00:38:45 +02:00
result = self . client_get ( " /json/messages " , params )
2016-07-26 23:53:14 +02:00
self . assert_json_error_contains ( result , ' elem[ " operand " ] is not a string ' )
2016-06-21 21:05:44 +02:00
def exercise_bad_narrow_operand ( self , operator , operands , error_msg ) :
2016-12-08 09:39:48 +01:00
# type: (Text, Sequence, Text) -> None
other_params = [ ( " anchor " , 0 ) , ( " num_before " , 0 ) , ( " num_after " , 0 ) ] # type: List
2016-06-21 21:05:44 +02:00
for operand in operands :
post_params = dict ( other_params + [
( " narrow " , ujson . dumps ( [ [ operator , operand ] ] ) ) ] )
2016-07-28 00:38:45 +02:00
result = self . client_get ( " /json/messages " , post_params )
2016-06-21 21:05:44 +02:00
self . assert_json_error_contains ( result , error_msg )
def test_bad_narrow_stream_content ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
2017-03-24 07:51:46 +01:00
If an invalid stream name is requested in get_messages , an error is
2016-06-21 21:05:44 +02:00
returned .
"""
self . login ( " hamlet@zulip.com " )
2016-12-08 09:39:48 +01:00
bad_stream_content = ( 0 , [ ] , [ " x " , " y " ] ) # type: Sequence
2016-06-21 21:05:44 +02:00
self . exercise_bad_narrow_operand ( " stream " , bad_stream_content ,
2016-12-03 00:04:17 +01:00
" Bad value for ' narrow ' " )
2016-06-21 21:05:44 +02:00
def test_bad_narrow_one_on_one_email_content ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
"""
2017-03-24 07:51:46 +01:00
If an invalid ' pm-with ' is requested in get_messages , an
2016-06-21 21:05:44 +02:00
error is returned .
"""
self . login ( " hamlet@zulip.com " )
2016-12-08 09:39:48 +01:00
bad_stream_content = ( 0 , [ ] , [ " x " , " y " ] ) # type: Tuple[int, List[None], List[Text]]
2016-06-21 21:05:44 +02:00
self . exercise_bad_narrow_operand ( " pm-with " , bad_stream_content ,
2016-12-03 00:04:17 +01:00
" Bad value for ' narrow ' " )
2016-06-21 21:05:44 +02:00
def test_bad_narrow_nonexistent_stream ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
self . login ( " hamlet@zulip.com " )
self . exercise_bad_narrow_operand ( " stream " , [ ' non-existent stream ' ] ,
2016-12-03 00:04:17 +01:00
" Invalid narrow operator: unknown stream " )
2016-06-21 21:05:44 +02:00
def test_bad_narrow_nonexistent_email ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
self . login ( " hamlet@zulip.com " )
self . exercise_bad_narrow_operand ( " pm-with " , [ ' non-existent-user@zulip.com ' ] ,
2016-12-03 00:04:17 +01:00
" Invalid narrow operator: unknown user " )
2016-06-21 21:05:44 +02:00
def test_message_without_rendered_content ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
""" Older messages may not have rendered_content in the database """
m = self . get_last_message ( )
m . rendered_content = m . rendered_content_version = None
m . content = ' test content '
2016-07-08 02:35:05 +02:00
# Use to_dict_uncached_helper directly to avoid having to deal with remote cache
2016-10-04 15:52:26 +02:00
d = MessageDict . to_dict_uncached_helper ( m , True )
2016-06-21 21:05:44 +02:00
self . assertEqual ( d [ ' content ' ] , ' <p>test content</p> ' )
2017-03-24 07:51:46 +01:00
def common_check_get_messages_query ( self , query_params , expected ) :
2016-12-08 09:39:48 +01:00
# type: (Dict[str, object], Text) -> None
2016-06-21 21:05:44 +02:00
user_profile = get_user_profile_by_email ( " hamlet@zulip.com " )
request = POSTRequestMock ( query_params , user_profile )
with queries_captured ( ) as queries :
2017-03-24 07:51:46 +01:00
get_messages_backend ( request , user_profile )
2016-06-21 21:05:44 +02:00
for query in queries :
2017-03-24 07:51:46 +01:00
if " /* get_messages */ " in query [ ' sql ' ] :
sql = str ( query [ ' sql ' ] ) . replace ( " /* get_messages */ " , ' ' )
2016-06-21 21:05:44 +02:00
self . assertEqual ( sql , expected )
return
2017-03-24 07:51:46 +01:00
raise AssertionError ( " get_messages query not found " )
2016-06-21 21:05:44 +02:00
2016-07-23 18:41:39 +02:00
def test_use_first_unread_anchor_with_some_unread_messages ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-07-23 18:41:39 +02:00
user_profile = get_user_profile_by_email ( " hamlet@zulip.com " )
# Have Othello send messages to Hamlet that he hasn't read.
self . send_message ( " othello@zulip.com " , " Scotland " , Recipient . STREAM )
last_message_id_to_hamlet = self . send_message ( " othello@zulip.com " , " hamlet@zulip.com " , Recipient . PERSONAL )
# Add a few messages that help us test that our query doesn't
# look at messages that are irrelevant to Hamlet.
self . send_message ( " othello@zulip.com " , " cordelia@zulip.com " , Recipient . PERSONAL )
self . send_message ( " othello@zulip.com " , " iago@zulip.com " , Recipient . PERSONAL )
query_params = dict (
use_first_unread_anchor = ' true ' ,
anchor = 0 ,
2017-02-22 23:32:43 +01:00
num_before = 10 ,
num_after = 10 ,
2016-07-23 18:41:39 +02:00
narrow = ' [] '
)
request = POSTRequestMock ( query_params , user_profile )
with queries_captured ( ) as all_queries :
2017-03-24 07:51:46 +01:00
get_messages_backend ( request , user_profile )
2016-07-23 18:41:39 +02:00
# Verify the query for old messages looks correct.
2017-03-24 07:51:46 +01:00
queries = [ q for q in all_queries if ' /* get_messages */ ' in q [ ' sql ' ] ]
2016-07-23 18:41:39 +02:00
self . assertEqual ( len ( queries ) , 1 )
sql = queries [ 0 ] [ ' sql ' ]
2017-02-23 05:50:15 +01:00
self . assertNotIn ( ' AND message_id = %s ' % ( LARGER_THAN_MAX_MESSAGE_ID , ) , sql )
2016-07-23 18:41:39 +02:00
self . assertIn ( ' ORDER BY message_id ASC ' , sql )
2017-02-22 23:32:43 +01:00
cond = ' WHERE user_profile_id = %d AND message_id >= %d ' % ( user_profile . id , last_message_id_to_hamlet )
self . assertIn ( cond , sql )
cond = ' WHERE user_profile_id = %d AND message_id <= %d ' % ( user_profile . id , last_message_id_to_hamlet - 1 )
2016-07-23 18:41:39 +02:00
self . assertIn ( cond , sql )
def test_use_first_unread_anchor_with_no_unread_messages ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-07-23 18:41:39 +02:00
user_profile = get_user_profile_by_email ( " hamlet@zulip.com " )
query_params = dict (
use_first_unread_anchor = ' true ' ,
anchor = 0 ,
2017-02-22 23:32:43 +01:00
num_before = 10 ,
num_after = 10 ,
2016-07-23 18:41:39 +02:00
narrow = ' [] '
)
request = POSTRequestMock ( query_params , user_profile )
with queries_captured ( ) as all_queries :
2017-03-24 07:51:46 +01:00
get_messages_backend ( request , user_profile )
2016-07-23 18:41:39 +02:00
# Next, verify the use_first_unread_anchor setting invokes
2017-02-23 05:50:15 +01:00
# the `message_id = LARGER_THAN_MAX_MESSAGE_ID` hack.
2017-03-24 07:51:46 +01:00
queries = [ q for q in all_queries if ' /* get_messages */ ' in q [ ' sql ' ] ]
2016-07-23 18:41:39 +02:00
self . assertEqual ( len ( queries ) , 1 )
2017-02-23 05:50:15 +01:00
self . assertIn ( ' AND message_id <= %d ' % ( LARGER_THAN_MAX_MESSAGE_ID - 1 , ) , queries [ 0 ] [ ' sql ' ] )
2017-02-22 23:39:40 +01:00
# There should not be an after_query in this case, since it'd be useless
2017-02-23 05:50:15 +01:00
self . assertNotIn ( ' AND message_id >= %d ' % ( LARGER_THAN_MAX_MESSAGE_ID , ) , queries [ 0 ] [ ' sql ' ] )
2016-07-23 18:41:39 +02:00
2016-07-23 18:07:08 +02:00
def test_use_first_unread_anchor_with_muted_topics ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-07-23 18:07:08 +02:00
"""
Test that our logic related to ` use_first_unread_anchor `
2017-02-23 05:50:15 +01:00
invokes the ` message_id = LARGER_THAN_MAX_MESSAGE_ID ` hack for
2017-03-24 07:51:46 +01:00
the ` / * get_messages * / ` query when relevant muting
2016-07-23 18:07:08 +02:00
is in effect .
This is a very arcane test on arcane , but very heavily
2017-03-24 07:51:46 +01:00
field - tested , logic in get_messages_backend ( ) . If
2016-07-23 18:07:08 +02:00
this test breaks , be absolutely sure you know what you ' re
doing .
"""
2017-01-04 05:30:48 +01:00
realm = get_realm ( ' zulip ' )
2016-10-21 22:59:59 +02:00
self . make_stream ( ' web stuff ' )
2016-06-21 21:05:44 +02:00
user_profile = get_user_profile_by_email ( " hamlet@zulip.com " )
2016-10-21 22:59:59 +02:00
user_profile . muted_topics = ujson . dumps ( [ [ ' Scotland ' , ' golf ' ] , [ ' web stuff ' , ' css ' ] , [ ' bogus ' , ' bogus ' ] ] )
2016-06-21 21:05:44 +02:00
user_profile . save ( )
query_params = dict (
use_first_unread_anchor = ' true ' ,
anchor = 0 ,
num_before = 0 ,
num_after = 0 ,
narrow = ' [[ " stream " , " Scotland " ]] '
)
request = POSTRequestMock ( query_params , user_profile )
2016-07-23 18:07:08 +02:00
with queries_captured ( ) as all_queries :
2017-03-24 07:51:46 +01:00
get_messages_backend ( request , user_profile )
2016-06-21 21:05:44 +02:00
2016-07-23 18:07:08 +02:00
# Do some tests on the main query, to verify the muting logic
# runs on this code path.
2016-12-08 09:39:48 +01:00
queries = [ q for q in all_queries if str ( q [ ' sql ' ] ) . startswith ( " SELECT message_id, flags " ) ]
2016-07-23 18:07:08 +02:00
self . assertEqual ( len ( queries ) , 1 )
stream = get_stream ( ' Scotland ' , realm )
recipient_id = get_recipient ( Recipient . STREAM , stream . id ) . id
cond = ''' AND NOT (recipient_id = {scotland} AND upper(subject) = upper( ' golf ' )) ''' . format ( scotland = recipient_id )
self . assertIn ( cond , queries [ 0 ] [ ' sql ' ] )
# Next, verify the use_first_unread_anchor setting invokes
2017-02-23 05:50:15 +01:00
# the `message_id = LARGER_THAN_MAX_MESSAGE_ID` hack.
2017-03-24 07:51:46 +01:00
queries = [ q for q in all_queries if ' /* get_messages */ ' in q [ ' sql ' ] ]
2016-07-23 18:07:08 +02:00
self . assertEqual ( len ( queries ) , 1 )
2017-02-23 05:50:15 +01:00
self . assertIn ( ' AND message_id = %d ' % ( LARGER_THAN_MAX_MESSAGE_ID , ) ,
queries [ 0 ] [ ' sql ' ] )
2016-06-21 21:05:44 +02:00
def test_exclude_muting_conditions ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2017-01-04 05:30:48 +01:00
realm = get_realm ( ' zulip ' )
2016-10-21 22:59:59 +02:00
self . make_stream ( ' web stuff ' )
2016-06-21 21:05:44 +02:00
user_profile = get_user_profile_by_email ( " hamlet@zulip.com " )
2016-07-23 17:07:38 +02:00
# Test the do-nothing case first.
user_profile . muted_topics = ujson . dumps ( [ [ ' irrelevant_stream ' , ' irrelevant_topic ' ] ] )
user_profile . save ( )
# If nothing relevant is muted, then exclude_muting_conditions()
# should return an empty list.
narrow = [
dict ( operator = ' stream ' , operand = ' Scotland ' ) ,
]
muting_conditions = exclude_muting_conditions ( user_profile , narrow )
self . assertEqual ( muting_conditions , [ ] )
# Ok, now set up our muted topics to include a topic relevant to our narrow.
2016-10-21 22:59:59 +02:00
user_profile . muted_topics = ujson . dumps ( [ [ ' Scotland ' , ' golf ' ] , [ ' web stuff ' , ' css ' ] , [ ' bogus ' , ' bogus ' ] ] )
2016-06-21 21:05:44 +02:00
user_profile . save ( )
2016-07-23 17:07:38 +02:00
# And verify that our query will exclude them.
2016-06-21 21:05:44 +02:00
narrow = [
dict ( operator = ' stream ' , operand = ' Scotland ' ) ,
]
muting_conditions = exclude_muting_conditions ( user_profile , narrow )
2017-02-22 22:13:57 +01:00
query = select ( [ column ( " id " ) . label ( " message_id " ) ] , None , table ( " zerver_message " ) )
2016-06-21 21:05:44 +02:00
query = query . where ( * muting_conditions )
expected_query = '''
SELECT id AS message_id
FROM zerver_message
WHERE NOT ( recipient_id = : recipient_id_1 AND upper ( subject ) = upper ( : upper_1 ) )
'''
self . assertEqual ( fix_ws ( query ) , fix_ws ( expected_query ) )
params = get_sqlalchemy_query_params ( query )
self . assertEqual ( params [ ' recipient_id_1 ' ] , get_recipient_id_for_stream_name ( realm , ' Scotland ' ) )
self . assertEqual ( params [ ' upper_1 ' ] , ' golf ' )
mute_stream ( realm , user_profile , ' Verona ' )
narrow = [ ]
muting_conditions = exclude_muting_conditions ( user_profile , narrow )
2017-02-22 22:13:57 +01:00
query = select ( [ column ( " id " ) ] , None , table ( " zerver_message " ) )
2016-06-21 21:05:44 +02:00
query = query . where ( and_ ( * muting_conditions ) )
expected_query = '''
SELECT id
FROM zerver_message
WHERE recipient_id NOT IN ( : recipient_id_1 )
AND NOT
( recipient_id = : recipient_id_2 AND upper ( subject ) = upper ( : upper_1 ) OR
recipient_id = : recipient_id_3 AND upper ( subject ) = upper ( : upper_2 ) ) '''
self . assertEqual ( fix_ws ( query ) , fix_ws ( expected_query ) )
params = get_sqlalchemy_query_params ( query )
self . assertEqual ( params [ ' recipient_id_1 ' ] , get_recipient_id_for_stream_name ( realm , ' Verona ' ) )
self . assertEqual ( params [ ' recipient_id_2 ' ] , get_recipient_id_for_stream_name ( realm , ' Scotland ' ) )
self . assertEqual ( params [ ' upper_1 ' ] , ' golf ' )
2016-10-21 22:59:59 +02:00
self . assertEqual ( params [ ' recipient_id_3 ' ] , get_recipient_id_for_stream_name ( realm , ' web stuff ' ) )
2016-06-21 21:05:44 +02:00
self . assertEqual ( params [ ' upper_2 ' ] , ' css ' )
2017-03-24 07:51:46 +01:00
def test_get_messages_queries ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
query_ids = self . get_query_ids ( )
sql_template = ' SELECT anon_1.message_id, anon_1.flags \n FROM (SELECT message_id, flags \n FROM zerver_usermessage \n WHERE user_profile_id = {hamlet_id} AND message_id >= 0 ORDER BY message_id ASC \n LIMIT 11) AS anon_1 ORDER BY message_id ASC '
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 } , sql )
2016-06-21 21:05:44 +02:00
sql_template = ' SELECT anon_1.message_id, anon_1.flags \n FROM (SELECT message_id, flags \n FROM zerver_usermessage \n WHERE user_profile_id = {hamlet_id} AND message_id <= 100 ORDER BY message_id DESC \n LIMIT 11) AS anon_1 ORDER BY message_id ASC '
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 100 , ' num_before ' : 10 , ' num_after ' : 0 } , sql )
2016-06-21 21:05:44 +02:00
sql_template = ' SELECT anon_1.message_id, anon_1.flags \n FROM ((SELECT message_id, flags \n FROM zerver_usermessage \n WHERE user_profile_id = {hamlet_id} AND message_id <= 99 ORDER BY message_id DESC \n LIMIT 10) UNION ALL (SELECT message_id, flags \n FROM zerver_usermessage \n WHERE user_profile_id = {hamlet_id} AND message_id >= 100 ORDER BY message_id ASC \n LIMIT 11)) AS anon_1 ORDER BY message_id ASC '
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 100 , ' num_before ' : 10 , ' num_after ' : 10 } , sql )
2016-06-21 21:05:44 +02:00
2017-03-24 07:51:46 +01:00
def test_get_messages_with_narrow_queries ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
query_ids = self . get_query_ids ( )
sql_template = ' SELECT anon_1.message_id, anon_1.flags \n FROM (SELECT message_id, flags \n FROM zerver_usermessage JOIN zerver_message ON zerver_usermessage.message_id = zerver_message.id \n WHERE user_profile_id = {hamlet_id} AND (sender_id = {othello_id} AND recipient_id = {hamlet_recipient} OR sender_id = {hamlet_id} AND recipient_id = {othello_recipient} ) AND message_id >= 0 ORDER BY message_id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC '
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " pm-with " , " othello@zulip.com " ]] ' } ,
sql )
2016-06-21 21:05:44 +02:00
sql_template = ' SELECT anon_1.message_id, anon_1.flags \n FROM (SELECT message_id, flags \n FROM zerver_usermessage JOIN zerver_message ON zerver_usermessage.message_id = zerver_message.id \n WHERE user_profile_id = {hamlet_id} AND (flags & 2) != 0 AND message_id >= 0 ORDER BY message_id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC '
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " is " , " starred " ]] ' } ,
sql )
2016-06-21 21:05:44 +02:00
sql_template = ' SELECT anon_1.message_id, anon_1.flags \n FROM (SELECT message_id, flags \n FROM zerver_usermessage JOIN zerver_message ON zerver_usermessage.message_id = zerver_message.id \n WHERE user_profile_id = {hamlet_id} AND sender_id = {othello_id} AND message_id >= 0 ORDER BY message_id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC '
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " sender " , " othello@zulip.com " ]] ' } ,
sql )
2016-06-21 21:05:44 +02:00
sql_template = ' SELECT anon_1.message_id \n FROM (SELECT id AS message_id \n FROM zerver_message \n WHERE recipient_id = {scotland_recipient} AND zerver_message.id >= 0 ORDER BY zerver_message.id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC '
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " stream " , " Scotland " ]] ' } ,
sql )
2016-06-21 21:05:44 +02:00
sql_template = " SELECT anon_1.message_id, anon_1.flags \n FROM (SELECT message_id, flags \n FROM zerver_usermessage JOIN zerver_message ON zerver_usermessage.message_id = zerver_message.id \n WHERE user_profile_id = {hamlet_id} AND upper(subject) = upper( ' blah ' ) AND message_id >= 0 ORDER BY message_id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC "
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " topic " , " blah " ]] ' } ,
sql )
2016-06-21 21:05:44 +02:00
sql_template = " SELECT anon_1.message_id \n FROM (SELECT id AS message_id \n FROM zerver_message \n WHERE recipient_id = {scotland_recipient} AND upper(subject) = upper( ' blah ' ) AND zerver_message.id >= 0 ORDER BY zerver_message.id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC "
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " stream " , " Scotland " ], [ " topic " , " blah " ]] ' } ,
sql )
2016-06-21 21:05:44 +02:00
# Narrow to pms with yourself
sql_template = ' SELECT anon_1.message_id, anon_1.flags \n FROM (SELECT message_id, flags \n FROM zerver_usermessage JOIN zerver_message ON zerver_usermessage.message_id = zerver_message.id \n WHERE user_profile_id = {hamlet_id} AND sender_id = {hamlet_id} AND recipient_id = {hamlet_recipient} AND message_id >= 0 ORDER BY message_id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC '
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " pm-with " , " hamlet@zulip.com " ]] ' } ,
sql )
2016-06-21 21:05:44 +02:00
sql_template = ' SELECT anon_1.message_id, anon_1.flags \n FROM (SELECT message_id, flags \n FROM zerver_usermessage JOIN zerver_message ON zerver_usermessage.message_id = zerver_message.id \n WHERE user_profile_id = {hamlet_id} AND recipient_id = {scotland_recipient} AND (flags & 2) != 0 AND message_id >= 0 ORDER BY message_id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC '
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " stream " , " Scotland " ], [ " is " , " starred " ]] ' } ,
sql )
2016-06-21 21:05:44 +02:00
2016-04-24 17:08:51 +02:00
@override_settings ( USING_PGROONGA = False )
2017-03-24 07:51:46 +01:00
def test_get_messages_with_search_queries ( self ) :
2016-12-08 09:39:48 +01:00
# type: () -> None
2016-06-21 21:05:44 +02:00
query_ids = self . get_query_ids ( )
2016-12-08 09:39:48 +01:00
sql_template = " SELECT anon_1.message_id, anon_1.flags, anon_1.subject, anon_1.rendered_content, anon_1.content_matches, anon_1.subject_matches \n FROM (SELECT message_id, flags, subject, rendered_content, ts_match_locs_array( ' zulip.english_us_search ' , rendered_content, plainto_tsquery( ' zulip.english_us_search ' , ' jumping ' )) AS content_matches, ts_match_locs_array( ' zulip.english_us_search ' , escape_html(subject), plainto_tsquery( ' zulip.english_us_search ' , ' jumping ' )) AS subject_matches \n FROM zerver_usermessage JOIN zerver_message ON zerver_usermessage.message_id = zerver_message.id \n WHERE user_profile_id = {hamlet_id} AND (search_tsvector @@ plainto_tsquery( ' zulip.english_us_search ' , ' jumping ' )) AND message_id >= 0 ORDER BY message_id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC " # type: Text
2016-06-21 21:05:44 +02:00
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " search " , " jumping " ]] ' } ,
sql )
2016-06-21 21:05:44 +02:00
sql_template = " SELECT anon_1.message_id, anon_1.subject, anon_1.rendered_content, anon_1.content_matches, anon_1.subject_matches \n FROM (SELECT id AS message_id, subject, rendered_content, ts_match_locs_array( ' zulip.english_us_search ' , rendered_content, plainto_tsquery( ' zulip.english_us_search ' , ' jumping ' )) AS content_matches, ts_match_locs_array( ' zulip.english_us_search ' , escape_html(subject), plainto_tsquery( ' zulip.english_us_search ' , ' jumping ' )) AS subject_matches \n FROM zerver_message \n WHERE recipient_id = {scotland_recipient} AND (search_tsvector @@ plainto_tsquery( ' zulip.english_us_search ' , ' jumping ' )) AND zerver_message.id >= 0 ORDER BY zerver_message.id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC "
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " stream " , " Scotland " ], [ " search " , " jumping " ]] ' } ,
sql )
2016-06-21 21:05:44 +02:00
sql_template = ' SELECT anon_1.message_id, anon_1.flags, anon_1.subject, anon_1.rendered_content, anon_1.content_matches, anon_1.subject_matches \n FROM (SELECT message_id, flags, subject, rendered_content, ts_match_locs_array( \' zulip.english_us_search \' , rendered_content, plainto_tsquery( \' zulip.english_us_search \' , \' " jumping " quickly \' )) AS content_matches, ts_match_locs_array( \' zulip.english_us_search \' , escape_html(subject), plainto_tsquery( \' zulip.english_us_search \' , \' " jumping " quickly \' )) AS subject_matches \n FROM zerver_usermessage JOIN zerver_message ON zerver_usermessage.message_id = zerver_message.id \n WHERE user_profile_id = {hamlet_id} AND (content ILIKE \' % jumping % \' OR subject ILIKE \' % jumping % \' ) AND (search_tsvector @@ plainto_tsquery( \' zulip.english_us_search \' , \' " jumping " quickly \' )) AND message_id >= 0 ORDER BY message_id ASC \n LIMIT 10) AS anon_1 ORDER BY message_id ASC '
sql = sql_template . format ( * * query_ids )
2017-03-24 07:51:46 +01:00
self . common_check_get_messages_query ( { ' anchor ' : 0 , ' num_before ' : 0 , ' num_after ' : 10 ,
' narrow ' : ' [[ " search " , " \\ " jumping \\ " quickly " ]] ' } ,
sql )
2017-04-29 00:03:43 +02:00
@override_settings ( USING_PGROONGA = False )
def test_get_messages_with_search_using_email ( self ) :
# type: () -> None
self . login ( " cordelia@zulip.com " )
messages_to_search = [
( ' say hello ' , ' How are you doing, @**Othello, the Moor of Venice**? ' ) ,
( ' lunch plans ' , ' I am hungry! ' ) ,
]
for topic , content in messages_to_search :
self . send_message (
sender_name = " cordelia@zulip.com " ,
raw_recipients = " Verona " ,
message_type = Recipient . STREAM ,
content = content ,
subject = topic ,
)
self . _update_tsvector_index ( )
narrow = [
dict ( operator = ' sender ' , operand = ' cordelia@zulip.com ' ) ,
dict ( operator = ' search ' , operand = ' othello@zulip.com ' ) ,
]
result = self . get_and_check_messages ( dict (
narrow = ujson . dumps ( narrow ) ,
anchor = 0 ,
num_after = 10 ,
) ) # type: Dict[str, Dict]
self . assertEqual ( len ( result [ ' messages ' ] ) , 0 )
narrow = [
dict ( operator = ' sender ' , operand = ' cordelia@zulip.com ' ) ,
dict ( operator = ' search ' , operand = ' othello ' ) ,
]
result = self . get_and_check_messages ( dict (
narrow = ujson . dumps ( narrow ) ,
anchor = 0 ,
num_after = 10 ,
2017-04-29 00:47:26 +02:00
) )
2017-04-29 00:03:43 +02:00
self . assertEqual ( len ( result [ ' messages ' ] ) , 1 )
messages = result [ ' messages ' ]
meeting_message = [ m for m in messages if m [ ' subject ' ] == ' say hello ' ] [ 0 ]
self . assertEqual (
meeting_message [ ' match_subject ' ] ,
' say hello ' )
self . assertEqual (
meeting_message [ ' match_content ' ] ,
' <p>How are you doing, <span class= " user-mention " data-user-email= " othello@zulip.com " data-user-id= " 6 " > ' +
' @<span class= " highlight " >Othello</span>, the Moor of Venice</span>?</p> ' )