2014-02-07 22:47:30 +01:00
# -*- coding: utf-8 -*-
2018-03-27 04:34:43 +02:00
import base64
2016-07-09 07:47:07 +02:00
import mock
2017-09-16 08:24:17 +02:00
import re
import os
from collections import defaultdict
2016-07-09 07:47:07 +02:00
2018-05-11 01:39:38 +02:00
from typing import Any , Dict , Iterable , List , Optional , Tuple
2017-07-12 10:16:02 +02:00
2019-02-02 23:53:44 +01:00
from django . test import TestCase
2016-12-28 14:43:07 +01:00
from django . http import HttpResponse , HttpRequest
2016-10-12 10:43:20 +02:00
from django . conf import settings
2016-05-18 20:35:35 +02:00
2017-06-27 13:10:16 +02:00
from zerver . forms import OurAuthenticationForm
2016-04-21 18:20:09 +02:00
from zerver . lib . actions import do_deactivate_realm , do_deactivate_user , \
2019-06-04 00:55:07 +02:00
do_reactivate_user , do_reactivate_realm , do_set_realm_property
2019-12-16 08:12:39 +01:00
from zerver . lib . exceptions import JsonableError , InvalidAPIKeyError , InvalidAPIKeyFormatError
2016-06-22 01:14:06 +02:00
from zerver . lib . initial_password import initial_password
2016-04-21 07:19:08 +02:00
from zerver . lib . test_helpers import (
2016-11-10 19:30:09 +01:00
HostRequestMock ,
)
from zerver . lib . test_classes import (
ZulipTestCase ,
2016-04-21 07:19:08 +02:00
)
2018-12-11 20:46:52 +01:00
from zerver . lib . response import json_response , json_success
2018-08-01 10:53:40 +02:00
from zerver . lib . users import get_api_key
2017-09-16 08:24:17 +02:00
from zerver . lib . user_agent import parse_user_agent
2019-12-16 06:27:34 +01:00
from zerver . lib . utils import generate_api_key , has_api_key_format
2016-05-29 16:52:55 +02:00
from zerver . lib . request import \
2014-02-14 19:56:55 +01:00
REQ , has_request_variables , RequestVariableMissingError , \
2018-11-09 19:45:25 +01:00
RequestVariableConversionError , RequestConfusingParmsError
2019-06-06 05:55:09 +02:00
from zerver . lib . webhooks . common import UnexpectedWebhookEventType
2016-07-09 07:47:07 +02:00
from zerver . decorator import (
api_key_only_webhook_view ,
2018-12-11 20:46:52 +01:00
authenticated_json_view ,
2018-03-27 04:34:43 +02:00
authenticated_rest_api_view ,
2018-12-11 20:46:52 +01:00
authenticated_uploads_api_view ,
2017-10-31 00:31:47 +01:00
authenticate_notify , cachify ,
2016-07-09 20:37:09 +02:00
get_client_name , internal_notify_view , is_local_addr ,
2019-02-02 23:53:44 +01:00
rate_limit , validate_api_key ,
2017-07-12 10:16:02 +02:00
return_success_on_head_request , to_not_negative_int_or_none ,
zulip_login_required
2017-01-24 06:34:26 +01:00
)
2019-04-18 04:35:14 +02:00
from zerver . lib . cache import ignore_unhashable_lru_cache , dict_to_items_tuple , items_tuple_to_dict
2014-02-14 19:56:55 +01:00
from zerver . lib . validator import (
2017-03-26 08:11:45 +02:00
check_string , check_dict , check_dict_only , check_bool , check_float , check_int , check_list , Validator ,
2018-05-03 23:21:16 +02:00
check_variable_type , equals , check_none_or , check_url , check_short_string ,
2019-02-09 23:57:54 +01:00
check_string_fixed_length , check_capped_string , check_color , to_non_negative_int ,
2019-11-16 17:38:27 +01:00
check_string_or_int_list , check_string_or_int , check_int_in
2014-02-14 19:56:55 +01:00
)
2016-04-21 07:19:08 +02:00
from zerver . models import \
2019-02-02 23:53:44 +01:00
get_realm , get_user , UserProfile , Realm
2014-02-07 22:47:30 +01:00
import ujson
class DecoratorTestCase ( TestCase ) :
2017-11-05 10:51:25 +01:00
def test_get_client_name ( self ) - > None :
2020-03-08 21:12:38 +01:00
req = HostRequestMock ( )
self . assertEqual ( get_client_name ( req ) , ' Unspecified ' )
req . META [ ' HTTP_USER_AGENT ' ] = ' ZulipElectron/4.0.3 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Zulip/4.0.3 Chrome/66.0.3359.181 Electron/3.1.10 Safari/537.36 '
self . assertEqual ( get_client_name ( req ) , ' ZulipElectron ' )
req . META [ ' HTTP_USER_AGENT ' ] = ' ZulipDesktop/0.4.4 (Mac) '
self . assertEqual ( get_client_name ( req ) , ' ZulipDesktop ' )
req . META [ ' HTTP_USER_AGENT ' ] = ' ZulipMobile/26.22.145 (Android 10) '
self . assertEqual ( get_client_name ( req ) , ' ZulipMobile ' )
req . META [ ' HTTP_USER_AGENT ' ] = ' ZulipMobile/26.22.145 (iOS 13.3.1) '
self . assertEqual ( get_client_name ( req ) , ' ZulipMobile ' )
# TODO: This should ideally be Firefox.
req . META [ ' HTTP_USER_AGENT ' ] = ' Mozilla/5.0 (X11; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0 '
self . assertEqual ( get_client_name ( req ) , ' Mozilla ' )
# TODO: This should ideally be Chrome.
req . META [ ' HTTP_USER_AGENT ' ] = ' Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.43 Safari/537.36 '
self . assertEqual ( get_client_name ( req ) , ' Mozilla ' )
# TODO: This should ideally be Mobile Safari if we had better user-agent parsing.
req . META [ ' HTTP_USER_AGENT ' ] = ' Mozilla/5.0 (Linux; Android 8.0.0; SM-G930F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Mobile Safari/537.36 '
self . assertEqual ( get_client_name ( req ) , ' Mozilla ' )
2016-07-09 11:10:10 +02:00
2018-11-09 19:45:25 +01:00
def test_REQ_aliases ( self ) - > None :
@has_request_variables
def double ( request : HttpRequest ,
x : int = REQ ( whence = ' number ' , aliases = [ ' x ' , ' n ' ] , converter = int ) ) - > int :
return x + x
class Request :
GET = { } # type: Dict[str, str]
POST = { } # type: Dict[str, str]
request = Request ( )
request . POST = dict ( bogus = ' 5555 ' )
with self . assertRaises ( RequestVariableMissingError ) :
double ( request )
request . POST = dict ( number = ' 3 ' )
self . assertEqual ( double ( request ) , 6 )
request . POST = dict ( x = ' 4 ' )
self . assertEqual ( double ( request ) , 8 )
request . POST = dict ( n = ' 5 ' )
self . assertEqual ( double ( request ) , 10 )
request . POST = dict ( number = ' 6 ' , x = ' 7 ' )
with self . assertRaises ( RequestConfusingParmsError ) as cm :
double ( request )
self . assertEqual ( str ( cm . exception ) , " Can ' t decide between ' number ' and ' x ' arguments " )
2017-11-05 10:51:25 +01:00
def test_REQ_converter ( self ) - > None :
2014-02-07 22:47:30 +01:00
2017-11-05 10:51:25 +01:00
def my_converter ( data : str ) - > List [ int ] :
2014-02-14 19:56:55 +01:00
lst = ujson . loads ( data )
if not isinstance ( lst , list ) :
raise ValueError ( ' not a list ' )
2014-02-11 16:37:56 +01:00
if 13 in lst :
2017-03-09 09:03:21 +01:00
raise JsonableError ( ' 13 is an unlucky number! ' )
2017-11-03 05:13:04 +01:00
return [ int ( elem ) for elem in lst ]
2014-02-11 16:37:56 +01:00
2014-02-07 22:47:30 +01:00
@has_request_variables
2017-11-05 10:51:25 +01:00
def get_total ( request : HttpRequest , numbers : Iterable [ int ] = REQ ( converter = my_converter ) ) - > int :
2014-02-07 22:47:30 +01:00
return sum ( numbers )
2017-11-05 11:49:43 +01:00
class Request :
2016-11-03 13:00:18 +01:00
GET = { } # type: Dict[str, str]
POST = { } # type: Dict[str, str]
2014-02-07 22:47:30 +01:00
request = Request ( )
with self . assertRaises ( RequestVariableMissingError ) :
get_total ( request )
2016-11-03 13:00:18 +01:00
request . POST [ ' numbers ' ] = ' bad_value '
2014-02-07 22:47:30 +01:00
with self . assertRaises ( RequestVariableConversionError ) as cm :
get_total ( request )
self . assertEqual ( str ( cm . exception ) , " Bad value for ' numbers ' : bad_value " )
2017-03-05 08:34:28 +01:00
request . POST [ ' numbers ' ] = ujson . dumps ( ' { fun: unfun} ' )
with self . assertRaises ( JsonableError ) as cm :
get_total ( request )
self . assertEqual ( str ( cm . exception ) , ' Bad value for \' numbers \' : " { fun: unfun} " ' )
2016-11-03 13:00:18 +01:00
request . POST [ ' numbers ' ] = ujson . dumps ( [ 2 , 3 , 5 , 8 , 13 , 21 ] )
2014-02-11 16:37:56 +01:00
with self . assertRaises ( JsonableError ) as cm :
get_total ( request )
self . assertEqual ( str ( cm . exception ) , " 13 is an unlucky number! " )
2016-11-03 13:00:18 +01:00
request . POST [ ' numbers ' ] = ujson . dumps ( [ 1 , 2 , 3 , 4 , 5 , 6 ] )
2014-02-07 22:47:30 +01:00
result = get_total ( request )
self . assertEqual ( result , 21 )
2017-11-05 10:51:25 +01:00
def test_REQ_converter_and_validator_invalid ( self ) - > None :
2017-03-09 09:16:15 +01:00
with self . assertRaisesRegex ( AssertionError , " converter and validator are mutually exclusive " ) :
2017-03-05 09:12:46 +01:00
@has_request_variables
2019-11-13 10:06:02 +01:00
def get_total ( request : HttpRequest ,
numbers : Iterable [ int ] = REQ ( validator = check_list ( check_int ) , # type: ignore # The condition being tested is in fact an error.
2017-11-20 03:22:57 +01:00
converter = lambda x : [ ] ) ) - > int :
2017-03-05 09:12:46 +01:00
return sum ( numbers ) # nocoverage -- isn't intended to be run
2017-11-05 10:51:25 +01:00
def test_REQ_validator ( self ) - > None :
2014-02-07 22:47:30 +01:00
@has_request_variables
2017-11-17 07:00:53 +01:00
def get_total ( request : HttpRequest ,
numbers : Iterable [ int ] = REQ ( validator = check_list ( check_int ) ) ) - > int :
2014-02-07 22:47:30 +01:00
return sum ( numbers )
2017-11-05 11:49:43 +01:00
class Request :
2017-05-07 17:18:34 +02:00
GET = { } # type: Dict[str, str]
POST = { } # type: Dict[str, str]
2014-02-07 22:47:30 +01:00
request = Request ( )
with self . assertRaises ( RequestVariableMissingError ) :
get_total ( request )
2016-11-03 13:00:18 +01:00
request . POST [ ' numbers ' ] = ' bad_value '
2014-02-07 22:47:30 +01:00
with self . assertRaises ( JsonableError ) as cm :
get_total ( request )
2017-06-26 13:12:20 +02:00
self . assertEqual ( str ( cm . exception ) , ' Argument " numbers " is not valid JSON. ' )
2014-02-07 22:47:30 +01:00
2016-11-03 13:00:18 +01:00
request . POST [ ' numbers ' ] = ujson . dumps ( [ 1 , 2 , " what? " , 4 , 5 , 6 ] )
2014-02-07 22:47:30 +01:00
with self . assertRaises ( JsonableError ) as cm :
get_total ( request )
self . assertEqual ( str ( cm . exception ) , ' numbers[2] is not an integer ' )
2016-11-03 13:00:18 +01:00
request . POST [ ' numbers ' ] = ujson . dumps ( [ 1 , 2 , 3 , 4 , 5 , 6 ] )
2014-02-07 22:47:30 +01:00
result = get_total ( request )
self . assertEqual ( result , 21 )
2018-05-04 00:22:00 +02:00
def test_REQ_str_validator ( self ) - > None :
@has_request_variables
def get_middle_characters ( request : HttpRequest ,
value : str = REQ ( str_validator = check_string_fixed_length ( 5 ) ) ) - > str :
return value [ 1 : - 1 ]
class Request :
GET = { } # type: Dict[str, str]
POST = { } # type: Dict[str, str]
request = Request ( )
with self . assertRaises ( RequestVariableMissingError ) :
get_middle_characters ( request )
request . POST [ ' value ' ] = ' long_value '
with self . assertRaises ( JsonableError ) as cm :
get_middle_characters ( request )
self . assertEqual ( str ( cm . exception ) , ' value has incorrect length 10; should be 5 ' )
request . POST [ ' value ' ] = ' valid '
result = get_middle_characters ( request )
self . assertEqual ( result , ' ali ' )
2017-11-05 10:51:25 +01:00
def test_REQ_argument_type ( self ) - > None :
2016-05-06 21:19:34 +02:00
@has_request_variables
2017-11-17 07:00:53 +01:00
def get_payload ( request : HttpRequest ,
payload : Dict [ str , Any ] = REQ ( argument_type = ' body ' ) ) - > Dict [ str , Any ] :
2016-05-06 21:19:34 +02:00
return payload
2020-02-14 09:32:06 +01:00
request = HostRequestMock ( )
2016-05-06 21:19:34 +02:00
request . body = ' notjson '
with self . assertRaises ( JsonableError ) as cm :
get_payload ( request )
self . assertEqual ( str ( cm . exception ) , ' Malformed JSON ' )
request . body = ' { " a " : " b " } '
self . assertEqual ( get_payload ( request ) , { ' a ' : ' b ' } )
# Test we properly handle an invalid argument_type.
with self . assertRaises ( Exception ) as cm :
@has_request_variables
2019-11-13 10:06:02 +01:00
def test ( request : HttpRequest ,
payload : Any = REQ ( argument_type = " invalid " ) ) - > None : # type: ignore # The condition being tested is in fact an error.
2017-11-05 10:51:25 +01:00
# Any is ok; exception should occur in decorator:
2017-03-05 08:34:28 +01:00
pass # nocoverage # this function isn't meant to be called
2016-05-06 21:19:34 +02:00
test ( request )
2017-11-05 10:51:25 +01:00
def test_api_key_only_webhook_view ( self ) - > None :
2016-05-18 20:35:35 +02:00
@api_key_only_webhook_view ( ' ClientName ' )
2018-05-11 01:39:38 +02:00
def my_webhook ( request : HttpRequest , user_profile : UserProfile ) - > str :
2016-07-09 10:12:46 +02:00
return user_profile . email
2016-05-18 20:35:35 +02:00
2017-07-19 05:08:51 +02:00
@api_key_only_webhook_view ( ' ClientName ' )
2017-11-05 10:51:25 +01:00
def my_webhook_raises_exception ( request : HttpRequest , user_profile : UserProfile ) - > None :
2017-07-19 05:08:51 +02:00
raise Exception ( " raised by webhook function " )
2019-06-06 05:55:09 +02:00
@api_key_only_webhook_view ( ' ClientName ' )
def my_webhook_raises_exception_unexpected_event (
request : HttpRequest , user_profile : UserProfile ) - > None :
raise UnexpectedWebhookEventType ( " helloworld " , " test_event " )
2016-05-18 20:35:35 +02:00
webhook_bot_email = ' webhook-bot@zulip.com '
2017-05-23 20:57:59 +02:00
webhook_bot_realm = get_realm ( ' zulip ' )
webhook_bot = get_user ( webhook_bot_email , webhook_bot_realm )
2018-08-01 10:53:40 +02:00
webhook_bot_api_key = get_api_key ( webhook_bot )
2017-07-19 05:08:51 +02:00
webhook_client_name = " ZulipClientNameWebhook "
2016-07-09 10:12:46 +02:00
2017-08-29 07:40:56 +02:00
request = HostRequestMock ( )
2019-12-16 08:12:39 +01:00
request . POST [ ' api_key ' ] = ' X ' * 32
2017-08-29 07:40:56 +02:00
2016-12-16 02:11:42 +01:00
with self . assertRaisesRegex ( JsonableError , " Invalid API key " ) :
2018-03-14 18:16:55 +01:00
my_webhook ( request ) # type: ignore # mypy doesn't seem to apply the decorator
2016-07-09 10:12:46 +02:00
# Start a valid request here
2016-11-03 13:00:18 +01:00
request . POST [ ' api_key ' ] = webhook_bot_api_key
2016-10-12 10:43:20 +02:00
2017-10-02 22:37:20 +02:00
with mock . patch ( ' logging.warning ' ) as mock_warning :
with self . assertRaisesRegex ( JsonableError ,
" Account is not associated with this subdomain " ) :
2018-03-14 18:16:55 +01:00
api_result = my_webhook ( request ) # type: ignore # mypy doesn't seem to apply the decorator
2016-10-12 10:43:20 +02:00
2017-10-02 22:37:20 +02:00
mock_warning . assert_called_with (
" User {} ( {} ) attempted to access API on wrong "
" subdomain ( {} ) " . format ( webhook_bot_email , ' zulip ' , ' ' ) )
2016-10-12 10:43:20 +02:00
2017-10-02 22:37:20 +02:00
with mock . patch ( ' logging.warning ' ) as mock_warning :
with self . assertRaisesRegex ( JsonableError ,
" Account is not associated with this subdomain " ) :
request . host = " acme. " + settings . EXTERNAL_HOST
2018-03-14 18:16:55 +01:00
api_result = my_webhook ( request ) # type: ignore # mypy doesn't seem to apply the decorator
2016-10-12 10:43:20 +02:00
2017-10-02 22:37:20 +02:00
mock_warning . assert_called_with (
" User {} ( {} ) attempted to access API on wrong "
" subdomain ( {} ) " . format ( webhook_bot_email , ' zulip ' , ' acme ' ) )
2016-10-12 10:43:20 +02:00
2017-08-29 07:40:56 +02:00
request . host = " zulip.testserver "
2017-07-19 05:08:51 +02:00
# Test when content_type is application/json and request.body
# is valid JSON; exception raised in the webhook function
# should be re-raised
with mock . patch ( ' zerver.decorator.webhook_logger.exception ' ) as mock_exception :
with self . assertRaisesRegex ( Exception , " raised by webhook function " ) :
request . body = " {} "
request . content_type = ' application/json '
2018-03-14 18:16:55 +01:00
my_webhook_raises_exception ( request ) # type: ignore # mypy doesn't seem to apply the decorator
2017-07-19 05:08:51 +02:00
# Test when content_type is not application/json; exception raised
# in the webhook function should be re-raised
with mock . patch ( ' zerver.decorator.webhook_logger.exception ' ) as mock_exception :
with self . assertRaisesRegex ( Exception , " raised by webhook function " ) :
request . body = " notjson "
request . content_type = ' text/plain '
2018-03-14 18:16:55 +01:00
my_webhook_raises_exception ( request ) # type: ignore # mypy doesn't seem to apply the decorator
2017-07-19 05:08:51 +02:00
2017-07-20 03:43:04 +02:00
# Test when content_type is application/json but request.body
# is not valid JSON; invalid JSON should be logged and the
# exception raised in the webhook function should be re-raised
with mock . patch ( ' zerver.decorator.webhook_logger.exception ' ) as mock_exception :
with self . assertRaisesRegex ( Exception , " raised by webhook function " ) :
request . body = " invalidjson "
request . content_type = ' application/json '
2018-03-29 21:04:05 +02:00
request . META [ ' HTTP_X_CUSTOM_HEADER ' ] = ' custom_value '
2018-03-14 18:16:55 +01:00
my_webhook_raises_exception ( request ) # type: ignore # mypy doesn't seem to apply the decorator
2017-07-20 03:43:04 +02:00
message = """
user : { email } ( { realm } )
client : { client_name }
URL : { path_info }
content_type : { content_type }
2018-03-29 21:04:05 +02:00
custom_http_headers :
{ custom_headers }
2017-07-20 03:43:04 +02:00
body :
2019-06-06 05:55:09 +02:00
{ body }
"""
message = message . strip ( ' ' )
mock_exception . assert_called_with ( message . format (
email = webhook_bot_email ,
realm = webhook_bot_realm . string_id ,
client_name = webhook_client_name ,
path_info = request . META . get ( ' PATH_INFO ' ) ,
content_type = request . content_type ,
custom_headers = " HTTP_X_CUSTOM_HEADER: custom_value \n " ,
body = request . body ,
) )
# Test when an unexpected webhook event occurs
with mock . patch ( ' zerver.decorator.webhook_unexpected_events_logger.exception ' ) as mock_exception :
exception_msg = " The ' test_event ' event isn ' t currently supported by the helloworld webhook "
with self . assertRaisesRegex ( UnexpectedWebhookEventType , exception_msg ) :
request . body = " invalidjson "
request . content_type = ' application/json '
request . META [ ' HTTP_X_CUSTOM_HEADER ' ] = ' custom_value '
my_webhook_raises_exception_unexpected_event ( request ) # type: ignore # mypy doesn't seem to apply the decorator
message = """
user : { email } ( { realm } )
client : { client_name }
URL : { path_info }
content_type : { content_type }
custom_http_headers :
{ custom_headers }
body :
2017-07-20 03:43:04 +02:00
{ body }
"""
2018-02-25 01:54:29 +01:00
message = message . strip ( ' ' )
2017-07-20 03:43:04 +02:00
mock_exception . assert_called_with ( message . format (
2017-07-19 05:08:51 +02:00
email = webhook_bot_email ,
realm = webhook_bot_realm . string_id ,
client_name = webhook_client_name ,
path_info = request . META . get ( ' PATH_INFO ' ) ,
content_type = request . content_type ,
2018-03-29 21:04:05 +02:00
custom_headers = " HTTP_X_CUSTOM_HEADER: custom_value \n " ,
2017-07-19 05:08:51 +02:00
body = request . body ,
) )
2016-07-09 10:12:46 +02:00
with self . settings ( RATE_LIMITING = True ) :
with mock . patch ( ' zerver.decorator.rate_limit_user ' ) as rate_limit_mock :
2018-03-14 18:16:55 +01:00
api_result = my_webhook ( request ) # type: ignore # mypy doesn't seem to apply the decorator
2016-07-09 10:12:46 +02:00
# Verify rate limiting was attempted.
self . assertTrue ( rate_limit_mock . called )
# Verify the main purpose of the decorator, which is that it passed in the
# user_profile to my_webhook, allowing it return the correct
# email for the bot (despite the API caller only knowing the API key).
self . assertEqual ( api_result , webhook_bot_email )
# Now deactivate the user
webhook_bot . is_active = False
webhook_bot . save ( )
2018-08-10 00:58:31 +02:00
with self . assertRaisesRegex ( JsonableError , " Account is deactivated " ) :
2018-03-14 18:16:55 +01:00
my_webhook ( request ) # type: ignore # mypy doesn't seem to apply the decorator
2016-07-09 10:12:46 +02:00
# Reactive the user, but deactivate their realm.
webhook_bot . is_active = True
webhook_bot . save ( )
webhook_bot . realm . deactivated = True
webhook_bot . realm . save ( )
2018-03-17 00:51:11 +01:00
with self . assertRaisesRegex ( JsonableError , " This organization has been deactivated " ) :
2018-03-14 18:16:55 +01:00
my_webhook ( request ) # type: ignore # mypy doesn't seem to apply the decorator
2016-05-18 20:35:35 +02:00
2018-12-11 20:46:52 +01:00
class SkipRateLimitingTest ( ZulipTestCase ) :
def test_authenticated_rest_api_view ( self ) - > None :
@authenticated_rest_api_view ( skip_rate_limiting = False )
def my_rate_limited_view ( request : HttpRequest , user_profile : UserProfile ) - > str :
return json_success ( ) # nocoverage # mock prevents this from being called
@authenticated_rest_api_view ( skip_rate_limiting = True )
def my_unlimited_view ( request : HttpRequest , user_profile : UserProfile ) - > str :
return json_success ( )
request = HostRequestMock ( host = " zulip.testserver " )
request . META [ ' HTTP_AUTHORIZATION ' ] = self . encode_credentials ( self . example_email ( " hamlet " ) )
request . method = ' POST '
with mock . patch ( ' zerver.decorator.rate_limit ' ) as rate_limit_mock :
result = my_unlimited_view ( request ) # type: ignore # mypy doesn't seem to apply the decorator
self . assert_json_success ( result )
self . assertFalse ( rate_limit_mock . called )
with mock . patch ( ' zerver.decorator.rate_limit ' ) as rate_limit_mock :
result = my_rate_limited_view ( request ) # type: ignore # mypy doesn't seem to apply the decorator
# Don't assert json_success, since it'll be the rate_limit mock object
self . assertTrue ( rate_limit_mock . called )
def test_authenticated_uploads_api_view ( self ) - > None :
@authenticated_uploads_api_view ( skip_rate_limiting = False )
def my_rate_limited_view ( request : HttpRequest , user_profile : UserProfile ) - > str :
return json_success ( ) # nocoverage # mock prevents this from being called
@authenticated_uploads_api_view ( skip_rate_limiting = True )
def my_unlimited_view ( request : HttpRequest , user_profile : UserProfile ) - > str :
return json_success ( )
request = HostRequestMock ( host = " zulip.testserver " )
request . method = ' POST '
request . POST [ ' api_key ' ] = get_api_key ( self . example_user ( " hamlet " ) )
with mock . patch ( ' zerver.decorator.rate_limit ' ) as rate_limit_mock :
result = my_unlimited_view ( request ) # type: ignore # mypy doesn't seem to apply the decorator
self . assert_json_success ( result )
self . assertFalse ( rate_limit_mock . called )
with mock . patch ( ' zerver.decorator.rate_limit ' ) as rate_limit_mock :
result = my_rate_limited_view ( request ) # type: ignore # mypy doesn't seem to apply the decorator
# Don't assert json_success, since it'll be the rate_limit mock object
self . assertTrue ( rate_limit_mock . called )
def test_authenticated_json_view ( self ) - > None :
def my_view ( request : HttpRequest , user_profile : UserProfile ) - > str :
return json_success ( )
my_rate_limited_view = authenticated_json_view ( my_view , skip_rate_limiting = False )
my_unlimited_view = authenticated_json_view ( my_view , skip_rate_limiting = True )
request = HostRequestMock ( host = " zulip.testserver " )
request . method = ' POST '
request . is_authenticated = True # type: ignore # HostRequestMock doesn't have is_authenticated
request . user = self . example_user ( " hamlet " )
with mock . patch ( ' zerver.decorator.rate_limit ' ) as rate_limit_mock :
result = my_unlimited_view ( request ) # type: ignore # mypy doesn't seem to apply the decorator
self . assert_json_success ( result )
self . assertFalse ( rate_limit_mock . called )
with mock . patch ( ' zerver.decorator.rate_limit ' ) as rate_limit_mock :
result = my_rate_limited_view ( request ) # type: ignore # mypy doesn't seem to apply the decorator
# Don't assert json_success, since it'll be the rate_limit mock object
self . assertTrue ( rate_limit_mock . called )
2018-03-27 04:34:43 +02:00
class DecoratorLoggingTestCase ( ZulipTestCase ) :
def test_authenticated_rest_api_view_logging ( self ) - > None :
@authenticated_rest_api_view ( webhook_client_name = " ClientName " )
def my_webhook_raises_exception ( request : HttpRequest , user_profile : UserProfile ) - > None :
raise Exception ( " raised by webhook function " )
webhook_bot_email = ' webhook-bot@zulip.com '
webhook_bot_realm = get_realm ( ' zulip ' )
request = HostRequestMock ( )
request . META [ ' HTTP_AUTHORIZATION ' ] = self . encode_credentials ( webhook_bot_email )
request . method = ' POST '
request . host = " zulip.testserver "
request . body = ' {} '
request . POST [ ' payload ' ] = ' {} '
request . content_type = ' text/plain '
with mock . patch ( ' zerver.decorator.webhook_logger.exception ' ) as mock_exception :
with self . assertRaisesRegex ( Exception , " raised by webhook function " ) :
my_webhook_raises_exception ( request ) # type: ignore # mypy doesn't seem to apply the decorator
message = """
user : { email } ( { realm } )
client : { client_name }
URL : { path_info }
content_type : { content_type }
custom_http_headers :
{ custom_headers }
body :
2019-06-06 05:55:09 +02:00
{ body }
"""
message = message . strip ( ' ' )
mock_exception . assert_called_with ( message . format (
email = webhook_bot_email ,
realm = webhook_bot_realm . string_id ,
client_name = ' ZulipClientNameWebhook ' ,
path_info = request . META . get ( ' PATH_INFO ' ) ,
content_type = request . content_type ,
custom_headers = None ,
body = request . body ,
) )
def test_authenticated_rest_api_view_logging_unexpected_event ( self ) - > None :
@authenticated_rest_api_view ( webhook_client_name = " ClientName " )
def my_webhook_raises_exception ( request : HttpRequest , user_profile : UserProfile ) - > None :
raise UnexpectedWebhookEventType ( " helloworld " , " test_event " )
webhook_bot_email = ' webhook-bot@zulip.com '
webhook_bot_realm = get_realm ( ' zulip ' )
request = HostRequestMock ( )
request . META [ ' HTTP_AUTHORIZATION ' ] = self . encode_credentials ( webhook_bot_email )
request . method = ' POST '
request . host = " zulip.testserver "
request . body = ' {} '
request . POST [ ' payload ' ] = ' {} '
request . content_type = ' text/plain '
with mock . patch ( ' zerver.decorator.webhook_unexpected_events_logger.exception ' ) as mock_exception :
exception_msg = " The ' test_event ' event isn ' t currently supported by the helloworld webhook "
with self . assertRaisesRegex ( UnexpectedWebhookEventType , exception_msg ) :
my_webhook_raises_exception ( request ) # type: ignore # mypy doesn't seem to apply the decorator
message = """
user : { email } ( { realm } )
client : { client_name }
URL : { path_info }
content_type : { content_type }
custom_http_headers :
{ custom_headers }
body :
2018-03-27 04:34:43 +02:00
{ body }
"""
message = message . strip ( ' ' )
mock_exception . assert_called_with ( message . format (
email = webhook_bot_email ,
realm = webhook_bot_realm . string_id ,
client_name = ' ZulipClientNameWebhook ' ,
path_info = request . META . get ( ' PATH_INFO ' ) ,
content_type = request . content_type ,
custom_headers = None ,
body = request . body ,
) )
def test_authenticated_rest_api_view_with_non_webhook_view ( self ) - > None :
@authenticated_rest_api_view ( )
def non_webhook_view_raises_exception ( request : HttpRequest , user_profile : UserProfile = None ) - > None :
raise Exception ( " raised by a non-webhook view " )
request = HostRequestMock ( )
request . META [ ' HTTP_AUTHORIZATION ' ] = self . encode_credentials ( " aaron@zulip.com " )
request . method = ' POST '
request . host = " zulip.testserver "
request . body = ' {} '
request . content_type = ' application/json '
with mock . patch ( ' zerver.decorator.webhook_logger.exception ' ) as mock_exception :
with self . assertRaisesRegex ( Exception , " raised by a non-webhook view " ) :
non_webhook_view_raises_exception ( request )
self . assertFalse ( mock_exception . called )
2018-04-26 07:14:21 +02:00
def test_authenticated_rest_api_view_errors ( self ) - > None :
user_profile = self . example_user ( " hamlet " )
2018-08-01 10:53:40 +02:00
api_key = get_api_key ( user_profile )
credentials = " %s : %s " % ( user_profile . email , api_key )
2018-04-26 07:14:21 +02:00
api_auth = ' Digest ' + base64 . b64encode ( credentials . encode ( ' utf-8 ' ) ) . decode ( ' utf-8 ' )
result = self . client_post ( ' /api/v1/external/zendesk ' , { } ,
HTTP_AUTHORIZATION = api_auth )
self . assert_json_error ( result , " This endpoint requires HTTP basic authentication. " )
api_auth = ' Basic ' + base64 . b64encode ( " foo " . encode ( ' utf-8 ' ) ) . decode ( ' utf-8 ' )
result = self . client_post ( ' /api/v1/external/zendesk ' , { } ,
HTTP_AUTHORIZATION = api_auth )
self . assert_json_error ( result , " Invalid authorization header for basic auth " ,
status_code = 401 )
result = self . client_post ( ' /api/v1/external/zendesk ' , { } )
self . assert_json_error ( result , " Missing authorization header for basic auth " ,
status_code = 401 )
2016-07-09 07:47:07 +02:00
class RateLimitTestCase ( TestCase ) :
2018-07-27 23:47:33 +02:00
def errors_disallowed ( self ) - > Any :
2016-07-09 07:47:07 +02:00
# Due to what is probably a hack in rate_limit(),
# some tests will give a false positive (or succeed
# for the wrong reason), unless we complain
# about logging errors. There might be a more elegant way
# make logging errors fail than what I'm doing here.
class TestLoggingErrorException ( Exception ) :
pass
return mock . patch ( ' logging.error ' , side_effect = TestLoggingErrorException )
2017-11-05 10:51:25 +01:00
def test_internal_local_clients_skip_rate_limiting ( self ) - > None :
2017-11-05 11:49:43 +01:00
class Client :
2016-07-09 07:47:07 +02:00
name = ' internal '
2016-11-29 07:22:02 +01:00
2017-11-05 11:49:43 +01:00
class Request :
2016-07-09 07:47:07 +02:00
client = Client ( )
META = { ' REMOTE_ADDR ' : ' 127.0.0.1 ' }
req = Request ( )
2017-11-05 10:51:25 +01:00
def f ( req : Any ) - > str :
2016-07-09 07:47:07 +02:00
return ' some value '
f = rate_limit ( ) ( f )
with self . settings ( RATE_LIMITING = True ) :
with mock . patch ( ' zerver.decorator.rate_limit_user ' ) as rate_limit_mock :
with self . errors_disallowed ( ) :
self . assertEqual ( f ( req ) , ' some value ' )
self . assertFalse ( rate_limit_mock . called )
2017-11-05 10:51:25 +01:00
def test_debug_clients_skip_rate_limiting ( self ) - > None :
2017-11-05 11:49:43 +01:00
class Client :
2016-07-09 07:47:07 +02:00
name = ' internal '
2016-11-29 07:22:02 +01:00
2017-11-05 11:49:43 +01:00
class Request :
2016-07-09 07:47:07 +02:00
client = Client ( )
META = { ' REMOTE_ADDR ' : ' 3.3.3.3 ' }
req = Request ( )
2017-11-05 10:51:25 +01:00
def f ( req : Any ) - > str :
2016-07-09 07:47:07 +02:00
return ' some value '
f = rate_limit ( ) ( f )
with self . settings ( RATE_LIMITING = True ) :
with mock . patch ( ' zerver.decorator.rate_limit_user ' ) as rate_limit_mock :
with self . errors_disallowed ( ) :
2016-07-30 04:29:58 +02:00
with self . settings ( DEBUG_RATE_LIMITING = True ) :
2016-07-09 07:47:07 +02:00
self . assertEqual ( f ( req ) , ' some value ' )
self . assertFalse ( rate_limit_mock . called )
2017-11-05 10:51:25 +01:00
def test_rate_limit_setting_of_false_bypasses_rate_limiting ( self ) - > None :
2017-11-05 11:49:43 +01:00
class Client :
2016-07-09 07:47:07 +02:00
name = ' external '
2016-11-29 07:22:02 +01:00
2017-11-05 11:49:43 +01:00
class Request :
2016-07-09 07:47:07 +02:00
client = Client ( )
META = { ' REMOTE_ADDR ' : ' 3.3.3.3 ' }
2017-05-07 17:18:34 +02:00
user = ' stub ' # any non-None value here exercises the correct code path
2016-07-09 07:47:07 +02:00
req = Request ( )
2017-11-05 10:51:25 +01:00
def f ( req : Any ) - > str :
2016-07-09 07:47:07 +02:00
return ' some value '
f = rate_limit ( ) ( f )
with self . settings ( RATE_LIMITING = False ) :
with mock . patch ( ' zerver.decorator.rate_limit_user ' ) as rate_limit_mock :
with self . errors_disallowed ( ) :
self . assertEqual ( f ( req ) , ' some value ' )
self . assertFalse ( rate_limit_mock . called )
2017-11-05 10:51:25 +01:00
def test_rate_limiting_happens_in_normal_case ( self ) - > None :
2017-11-05 11:49:43 +01:00
class Client :
2016-07-09 07:47:07 +02:00
name = ' external '
2016-11-29 07:22:02 +01:00
2017-11-05 11:49:43 +01:00
class Request :
2016-07-09 07:47:07 +02:00
client = Client ( )
META = { ' REMOTE_ADDR ' : ' 3.3.3.3 ' }
2017-05-07 17:18:34 +02:00
user = ' stub ' # any non-None value here exercises the correct code path
2016-07-09 07:47:07 +02:00
req = Request ( )
2017-11-05 10:51:25 +01:00
def f ( req : Any ) - > str :
2016-07-09 07:47:07 +02:00
return ' some value '
f = rate_limit ( ) ( f )
with self . settings ( RATE_LIMITING = True ) :
with mock . patch ( ' zerver.decorator.rate_limit_user ' ) as rate_limit_mock :
with self . errors_disallowed ( ) :
self . assertEqual ( f ( req ) , ' some value ' )
self . assertTrue ( rate_limit_mock . called )
2014-02-07 22:47:30 +01:00
class ValidatorTestCase ( TestCase ) :
2017-11-05 10:51:25 +01:00
def test_check_string ( self ) - > None :
2017-05-07 17:18:34 +02:00
x = " hello " # type: Any
2014-02-07 22:47:30 +01:00
self . assertEqual ( check_string ( ' x ' , x ) , None )
x = 4
self . assertEqual ( check_string ( ' x ' , x ) , ' x is not a string ' )
2018-05-03 23:22:05 +02:00
def test_check_string_fixed_length ( self ) - > None :
x = " hello " # type: Any
self . assertEqual ( check_string_fixed_length ( 5 ) ( ' x ' , x ) , None )
x = 4
self . assertEqual ( check_string_fixed_length ( 5 ) ( ' x ' , x ) , ' x is not a string ' )
x = " helloz "
self . assertEqual ( check_string_fixed_length ( 5 ) ( ' x ' , x ) , ' x has incorrect length 6; should be 5 ' )
x = " hi "
self . assertEqual ( check_string_fixed_length ( 5 ) ( ' x ' , x ) , ' x has incorrect length 2; should be 5 ' )
2018-05-03 23:21:16 +02:00
def test_check_capped_string ( self ) - > None :
x = " hello " # type: Any
self . assertEqual ( check_capped_string ( 5 ) ( ' x ' , x ) , None )
x = 4
self . assertEqual ( check_capped_string ( 5 ) ( ' x ' , x ) , ' x is not a string ' )
x = " helloz "
self . assertEqual ( check_capped_string ( 5 ) ( ' x ' , x ) , ' x is too long (limit: 5 characters) ' )
x = " hi "
self . assertEqual ( check_capped_string ( 5 ) ( ' x ' , x ) , None )
2019-11-16 17:38:27 +01:00
def test_check_int_in ( self ) - > None :
self . assertEqual ( check_int_in ( [ 1 ] ) ( " Test " , 1 ) , None )
self . assertEqual ( check_int_in ( [ 1 ] ) ( " Test " , 2 ) , " Invalid Test " )
self . assertEqual ( check_int_in ( [ 1 ] ) ( " Test " , " t " ) , " Test is not an integer " )
2017-11-05 10:51:25 +01:00
def test_check_short_string ( self ) - > None :
2017-11-11 01:37:43 +01:00
x = " hello " # type: Any
self . assertEqual ( check_short_string ( ' x ' , x ) , None )
x = ' x ' * 201
2018-05-03 23:21:16 +02:00
self . assertEqual ( check_short_string ( ' x ' , x ) , " x is too long (limit: 50 characters) " )
2017-11-11 01:37:43 +01:00
x = 4
self . assertEqual ( check_short_string ( ' x ' , x ) , ' x is not a string ' )
2017-11-05 10:51:25 +01:00
def test_check_bool ( self ) - > None :
2017-05-07 17:18:34 +02:00
x = True # type: Any
2014-02-07 22:47:30 +01:00
self . assertEqual ( check_bool ( ' x ' , x ) , None )
x = 4
self . assertEqual ( check_bool ( ' x ' , x ) , ' x is not a boolean ' )
2017-11-05 10:51:25 +01:00
def test_check_int ( self ) - > None :
2017-05-07 17:18:34 +02:00
x = 5 # type: Any
2014-02-07 22:47:30 +01:00
self . assertEqual ( check_int ( ' x ' , x ) , None )
x = [ { } ]
self . assertEqual ( check_int ( ' x ' , x ) , ' x is not an integer ' )
2019-02-09 23:57:54 +01:00
def test_to_non_negative_int ( self ) - > None :
self . assertEqual ( to_non_negative_int ( ' 5 ' ) , 5 )
with self . assertRaisesRegex ( ValueError , ' argument is negative ' ) :
self . assertEqual ( to_non_negative_int ( ' -1 ' ) )
with self . assertRaisesRegex ( ValueError , re . escape ( ' 5 is too large (max 4) ' ) ) :
self . assertEqual ( to_non_negative_int ( ' 5 ' , max_int_size = 4 ) )
with self . assertRaisesRegex ( ValueError , re . escape ( ' %s is too large (max %s ) ' % ( 2 * * 32 , 2 * * 32 - 1 ) ) ) :
self . assertEqual ( to_non_negative_int ( str ( 2 * * 32 ) ) )
2018-04-26 06:59:30 +02:00
def test_check_to_not_negative_int_or_none ( self ) - > None :
self . assertEqual ( to_not_negative_int_or_none ( ' 5 ' ) , 5 )
self . assertEqual ( to_not_negative_int_or_none ( None ) , None )
with self . assertRaises ( ValueError ) :
to_not_negative_int_or_none ( ' -5 ' )
2017-11-05 10:51:25 +01:00
def test_check_float ( self ) - > None :
2017-05-07 17:18:34 +02:00
x = 5.5 # type: Any
2017-03-24 05:23:41 +01:00
self . assertEqual ( check_float ( ' x ' , x ) , None )
x = 5
self . assertEqual ( check_float ( ' x ' , x ) , ' x is not a float ' )
x = [ { } ]
self . assertEqual ( check_float ( ' x ' , x ) , ' x is not a float ' )
2019-01-14 07:28:04 +01:00
def test_check_color ( self ) - > None :
2019-03-11 19:36:00 +01:00
x = [ ' #000099 ' , ' #80ffaa ' , ' #80FFAA ' , ' #abcd12 ' , ' #ffff00 ' , ' #ff0 ' , ' #f00 ' ] # valid
2019-01-14 07:28:04 +01:00
y = [ ' 000099 ' , ' #80f_aa ' , ' #80fraa ' , ' #abcd1234 ' , ' blue ' ] # invalid
z = 5 # invalid
for hex_color in x :
error = check_color ( ' color ' , hex_color )
self . assertEqual ( error , None )
for hex_color in y :
error = check_color ( ' color ' , hex_color )
self . assertEqual ( error , ' color is not a valid hex color code ' )
error = check_color ( ' color ' , z )
self . assertEqual ( error , ' color is not a string ' )
2017-11-05 10:51:25 +01:00
def test_check_list ( self ) - > None :
2017-05-07 17:18:34 +02:00
x = 999 # type: Any
2014-02-07 22:47:30 +01:00
error = check_list ( check_string ) ( ' x ' , x )
self . assertEqual ( error , ' x is not a list ' )
x = [ " hello " , 5 ]
error = check_list ( check_string ) ( ' x ' , x )
self . assertEqual ( error , ' x[1] is not a string ' )
x = [ [ " yo " ] , [ " hello " , " goodbye " , 5 ] ]
error = check_list ( check_list ( check_string ) ) ( ' x ' , x )
self . assertEqual ( error , ' x[1][2] is not a string ' )
x = [ " hello " , " goodbye " , " hello again " ]
error = check_list ( check_string , length = 2 ) ( ' x ' , x )
self . assertEqual ( error , ' x should have exactly 2 items ' )
2017-11-05 10:51:25 +01:00
def test_check_dict ( self ) - > None :
2014-02-07 22:47:30 +01:00
keys = [
( ' names ' , check_list ( check_string ) ) ,
( ' city ' , check_string ) ,
2017-05-07 17:18:34 +02:00
] # type: List[Tuple[str, Validator]]
2014-02-07 22:47:30 +01:00
x = {
' names ' : [ ' alice ' , ' bob ' ] ,
' city ' : ' Boston ' ,
2017-05-07 17:18:34 +02:00
} # type: Any
2014-02-07 22:47:30 +01:00
error = check_dict ( keys ) ( ' x ' , x )
self . assertEqual ( error , None )
x = 999
error = check_dict ( keys ) ( ' x ' , x )
self . assertEqual ( error , ' x is not a dict ' )
x = { }
error = check_dict ( keys ) ( ' x ' , x )
self . assertEqual ( error , ' names key is missing from x ' )
x = {
' names ' : [ ' alice ' , ' bob ' , { } ]
}
error = check_dict ( keys ) ( ' x ' , x )
self . assertEqual ( error , ' x[ " names " ][2] is not a string ' )
x = {
' names ' : [ ' alice ' , ' bob ' ] ,
' city ' : 5
}
error = check_dict ( keys ) ( ' x ' , x )
self . assertEqual ( error , ' x[ " city " ] is not a string ' )
2018-01-07 21:20:28 +01:00
x = {
' names ' : [ ' alice ' , ' bob ' ] ,
' city ' : ' Boston '
}
error = check_dict ( value_validator = check_string ) ( ' x ' , x )
self . assertEqual ( error , ' x contains a value that is not a string ' )
x = {
' city ' : ' Boston '
}
error = check_dict ( value_validator = check_string ) ( ' x ' , x )
self . assertEqual ( error , None )
2017-03-26 08:11:45 +02:00
# test dict_only
x = {
' names ' : [ ' alice ' , ' bob ' ] ,
' city ' : ' Boston ' ,
}
error = check_dict_only ( keys ) ( ' x ' , x )
self . assertEqual ( error , None )
x = {
' names ' : [ ' alice ' , ' bob ' ] ,
' city ' : ' Boston ' ,
' state ' : ' Massachusetts ' ,
}
error = check_dict_only ( keys ) ( ' x ' , x )
self . assertEqual ( error , ' Unexpected arguments: state ' )
2019-01-22 19:03:21 +01:00
# Test optional keys
optional_keys = [
( ' food ' , check_list ( check_string ) ) ,
( ' year ' , check_int )
]
x = {
' names ' : [ ' alice ' , ' bob ' ] ,
' city ' : ' Boston ' ,
' food ' : [ ' Lobster Spaghetti ' ]
}
error = check_dict ( keys ) ( ' x ' , x )
self . assertEqual ( error , None ) # since _allow_only_listed_keys is False
error = check_dict_only ( keys ) ( ' x ' , x )
self . assertEqual ( error , ' Unexpected arguments: food ' )
error = check_dict_only ( keys , optional_keys ) ( ' x ' , x )
self . assertEqual ( error , None )
x = {
' names ' : [ ' alice ' , ' bob ' ] ,
' city ' : ' Boston ' ,
' food ' : ' Lobster Spaghetti '
}
error = check_dict_only ( keys , optional_keys ) ( ' x ' , x )
self . assertEqual ( error , ' x[ " food " ] is not a list ' )
2017-11-05 10:51:25 +01:00
def test_encapsulation ( self ) - > None :
2014-02-07 22:47:30 +01:00
# There might be situations where we want deep
# validation, but the error message should be customized.
# This is an example.
2017-11-05 10:51:25 +01:00
def check_person ( val : Any ) - > Optional [ str ] :
2014-02-07 22:47:30 +01:00
error = check_dict ( [
2016-12-28 14:43:07 +01:00
( ' name ' , check_string ) ,
( ' age ' , check_int ) ,
2014-02-07 22:47:30 +01:00
] ) ( ' _ ' , val )
if error :
return ' This is not a valid person '
2017-03-03 20:30:49 +01:00
return None
2014-02-07 22:47:30 +01:00
person = { ' name ' : ' King Lear ' , ' age ' : 42 }
self . assertEqual ( check_person ( person ) , None )
2016-12-28 14:43:07 +01:00
nonperson = ' misconfigured data '
self . assertEqual ( check_person ( nonperson ) , ' This is not a valid person ' )
2014-02-07 22:47:30 +01:00
2017-11-05 10:51:25 +01:00
def test_check_variable_type ( self ) - > None :
2017-05-07 17:18:34 +02:00
x = 5 # type: Any
2017-03-05 09:49:05 +01:00
self . assertEqual ( check_variable_type ( [ check_string , check_int ] ) ( ' x ' , x ) , None )
x = ' x '
self . assertEqual ( check_variable_type ( [ check_string , check_int ] ) ( ' x ' , x ) , None )
x = [ { } ]
self . assertEqual ( check_variable_type ( [ check_string , check_int ] ) ( ' x ' , x ) , ' x is not an allowed_type ' )
2017-11-05 10:51:25 +01:00
def test_equals ( self ) - > None :
2017-05-07 17:18:34 +02:00
x = 5 # type: Any
2017-03-05 09:49:05 +01:00
self . assertEqual ( equals ( 5 ) ( ' x ' , x ) , None )
self . assertEqual ( equals ( 6 ) ( ' x ' , x ) , ' x != 6 (5 is wrong) ' )
2017-11-05 10:51:25 +01:00
def test_check_none_or ( self ) - > None :
2017-05-07 17:18:34 +02:00
x = 5 # type: Any
2017-03-05 09:49:05 +01:00
self . assertEqual ( check_none_or ( check_int ) ( ' x ' , x ) , None )
x = None
self . assertEqual ( check_none_or ( check_int ) ( ' x ' , x ) , None )
x = ' x '
self . assertEqual ( check_none_or ( check_int ) ( ' x ' , x ) , ' x is not an integer ' )
2017-11-05 10:51:25 +01:00
def test_check_url ( self ) - > None :
2017-06-11 09:11:59 +02:00
url = " http://127.0.0.1:5002/ " # type: Any
2018-03-16 06:22:14 +01:00
self . assertEqual ( check_url ( ' url ' , url ) , None )
2017-06-11 09:11:59 +02:00
url = " http://zulip-bots.example.com/ "
2018-03-16 06:22:14 +01:00
self . assertEqual ( check_url ( ' url ' , url ) , None )
2017-06-11 09:11:59 +02:00
url = " http://127.0.0 "
2018-03-16 06:22:14 +01:00
self . assertEqual ( check_url ( ' url ' , url ) , ' url is not a URL ' )
url = 99.3
self . assertEqual ( check_url ( ' url ' , url ) , ' url is not a string ' )
2017-06-11 09:11:59 +02:00
2019-06-08 23:19:56 +02:00
def test_check_string_or_int_list ( self ) - > None :
x = " string " # type: Any
self . assertEqual ( check_string_or_int_list ( ' x ' , x ) , None )
x = [ 1 , 2 , 4 ]
self . assertEqual ( check_string_or_int_list ( ' x ' , x ) , None )
x = None
self . assertEqual ( check_string_or_int_list ( ' x ' , x ) , ' x is not a string or an integer list ' )
x = [ 1 , 2 , ' 3 ' ]
self . assertEqual ( check_string_or_int_list ( ' x ' , x ) , ' x[2] is not an integer ' )
2019-07-13 01:48:04 +02:00
def test_check_string_or_int ( self ) - > None :
x = " string " # type: Any
self . assertEqual ( check_string_or_int ( ' x ' , x ) , None )
x = 1
self . assertEqual ( check_string_or_int ( ' x ' , x ) , None )
x = None
self . assertEqual ( check_string_or_int ( ' x ' , x ) , ' x is not a string or integer ' )
2019-06-08 23:19:56 +02:00
2016-08-23 02:08:42 +02:00
class DeactivatedRealmTest ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_send_deactivated_realm ( self ) - > None :
2016-04-21 07:19:08 +02:00
"""
rest_dispatch rejects requests in a deactivated realm , both / json and api
"""
2017-01-04 05:30:48 +01:00
realm = get_realm ( " zulip " )
do_deactivate_realm ( get_realm ( " zulip " ) )
2016-04-21 07:19:08 +02:00
2016-07-28 00:30:22 +02:00
result = self . client_post ( " /json/messages " , { " type " : " private " ,
2016-04-21 07:19:08 +02:00
" content " : " Test message " ,
" client " : " test suite " ,
2017-05-25 02:08:35 +02:00
" to " : self . example_email ( " othello " ) } )
2016-04-21 07:19:08 +02:00
self . assert_json_error_contains ( result , " Not logged in " , status_code = 401 )
# Even if a logged-in session was leaked, it still wouldn't work
realm . deactivated = False
realm . save ( )
2017-05-25 01:40:26 +02:00
self . login ( self . example_email ( " hamlet " ) )
2016-04-21 07:19:08 +02:00
realm . deactivated = True
realm . save ( )
2016-07-28 00:30:22 +02:00
result = self . client_post ( " /json/messages " , { " type " : " private " ,
2016-04-21 07:19:08 +02:00
" content " : " Test message " ,
" client " : " test suite " ,
2017-05-25 02:08:35 +02:00
" to " : self . example_email ( " othello " ) } )
2016-04-21 07:19:08 +02:00
self . assert_json_error_contains ( result , " has been deactivated " , status_code = 400 )
2017-12-14 19:02:31 +01:00
result = self . api_post ( self . example_email ( " hamlet " ) ,
" /api/v1/messages " , { " type " : " private " ,
" content " : " Test message " ,
" client " : " test suite " ,
" to " : self . example_email ( " othello " ) } )
2016-04-21 07:19:08 +02:00
self . assert_json_error_contains ( result , " has been deactivated " , status_code = 401 )
2017-11-05 10:51:25 +01:00
def test_fetch_api_key_deactivated_realm ( self ) - > None :
2016-04-21 07:19:08 +02:00
"""
authenticated_json_view views fail in a deactivated realm
"""
2017-01-04 05:30:48 +01:00
realm = get_realm ( " zulip " )
2017-05-07 19:39:30 +02:00
user_profile = self . example_user ( ' hamlet ' )
email = user_profile . email
2016-04-21 07:19:08 +02:00
test_password = " abcd1234 "
user_profile . set_password ( test_password )
self . login ( email )
realm . deactivated = True
realm . save ( )
2016-07-28 00:30:22 +02:00
result = self . client_post ( " /json/fetch_api_key " , { " password " : test_password } )
2016-04-21 07:19:08 +02:00
self . assert_json_error_contains ( result , " has been deactivated " , status_code = 400 )
2017-11-05 10:51:25 +01:00
def test_webhook_deactivated_realm ( self ) - > None :
2016-04-21 07:19:08 +02:00
"""
Using a webhook while in a deactivated realm fails
"""
2017-01-04 05:30:48 +01:00
do_deactivate_realm ( get_realm ( " zulip " ) )
2017-08-25 08:23:13 +02:00
user_profile = self . example_user ( " hamlet " )
2018-08-01 10:53:40 +02:00
api_key = get_api_key ( user_profile )
url = " /api/v1/external/jira?api_key= %s &stream=jira_custom " % ( api_key , )
2018-04-20 03:57:21 +02:00
data = self . webhook_fixture_data ( ' jira ' , ' created_v2 ' )
2016-07-28 00:30:22 +02:00
result = self . client_post ( url , data ,
2016-04-21 07:19:08 +02:00
content_type = " application/json " )
self . assert_json_error_contains ( result , " has been deactivated " , status_code = 400 )
2016-04-21 18:20:09 +02:00
2016-08-23 02:08:42 +02:00
class LoginRequiredTest ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_login_required ( self ) - > None :
2016-04-22 00:56:39 +02:00
"""
Verifies the zulip_login_required decorator blocks deactivated users .
"""
2017-05-07 19:39:30 +02:00
user_profile = self . example_user ( ' hamlet ' )
email = user_profile . email
2016-04-22 00:56:39 +02:00
# Verify fails if logged-out
2016-07-28 00:38:45 +02:00
result = self . client_get ( ' /accounts/accept_terms/ ' )
2016-04-22 00:56:39 +02:00
self . assertEqual ( result . status_code , 302 )
# Verify succeeds once logged-in
self . login ( email )
2016-07-28 00:38:45 +02:00
result = self . client_get ( ' /accounts/accept_terms/ ' )
2016-07-12 15:41:45 +02:00
self . assert_in_response ( " I agree to the " , result )
2016-04-22 00:56:39 +02:00
# Verify fails if user deactivated (with session still valid)
user_profile . is_active = False
user_profile . save ( )
2016-07-28 00:38:45 +02:00
result = self . client_get ( ' /accounts/accept_terms/ ' )
2016-04-22 00:56:39 +02:00
self . assertEqual ( result . status_code , 302 )
# Verify succeeds if user reactivated
do_reactivate_user ( user_profile )
self . login ( email )
2016-07-28 00:38:45 +02:00
result = self . client_get ( ' /accounts/accept_terms/ ' )
2016-07-12 15:41:45 +02:00
self . assert_in_response ( " I agree to the " , result )
2016-04-22 00:56:39 +02:00
# Verify fails if realm deactivated
user_profile . realm . deactivated = True
user_profile . realm . save ( )
2016-07-28 00:38:45 +02:00
result = self . client_get ( ' /accounts/accept_terms/ ' )
2016-04-22 00:56:39 +02:00
self . assertEqual ( result . status_code , 302 )
2016-08-23 02:08:42 +02:00
class FetchAPIKeyTest ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_fetch_api_key_success ( self ) - > None :
2017-05-25 01:50:35 +02:00
email = self . example_email ( " cordelia " )
2016-06-22 01:14:06 +02:00
self . login ( email )
2016-07-28 00:30:22 +02:00
result = self . client_post ( " /json/fetch_api_key " , { " password " : initial_password ( email ) } )
2016-06-22 01:14:06 +02:00
self . assert_json_success ( result )
2019-06-04 00:55:07 +02:00
def test_fetch_api_key_email_address_visibility ( self ) - > None :
user_profile = self . example_user ( " cordelia " )
email = user_profile . email
do_set_realm_property ( user_profile . realm , " email_address_visibility " ,
Realm . EMAIL_ADDRESS_VISIBILITY_ADMINS )
self . login ( email )
result = self . client_post ( " /json/fetch_api_key " ,
{ " password " : initial_password ( email ) } )
self . assert_json_success ( result )
2017-11-05 10:51:25 +01:00
def test_fetch_api_key_wrong_password ( self ) - > None :
2017-05-25 01:50:35 +02:00
email = self . example_email ( " cordelia " )
2016-06-22 01:14:06 +02:00
self . login ( email )
2016-07-28 00:30:22 +02:00
result = self . client_post ( " /json/fetch_api_key " , { " password " : " wrong_password " } )
2016-06-22 01:14:06 +02:00
self . assert_json_error_contains ( result , " password is incorrect " )
2016-08-23 02:08:42 +02:00
class InactiveUserTest ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_send_deactivated_user ( self ) - > None :
2016-04-21 18:20:09 +02:00
"""
rest_dispatch rejects requests from deactivated users , both / json and api
"""
2017-05-07 19:39:30 +02:00
user_profile = self . example_user ( ' hamlet ' )
email = user_profile . email
2016-04-21 18:20:09 +02:00
self . login ( email )
do_deactivate_user ( user_profile )
2016-07-28 00:30:22 +02:00
result = self . client_post ( " /json/messages " , { " type " : " private " ,
2016-04-21 18:20:09 +02:00
" content " : " Test message " ,
" client " : " test suite " ,
2017-05-25 02:08:35 +02:00
" to " : self . example_email ( " othello " ) } )
2016-04-21 18:20:09 +02:00
self . assert_json_error_contains ( result , " Not logged in " , status_code = 401 )
# Even if a logged-in session was leaked, it still wouldn't work
do_reactivate_user ( user_profile )
self . login ( email )
user_profile . is_active = False
user_profile . save ( )
2016-07-28 00:30:22 +02:00
result = self . client_post ( " /json/messages " , { " type " : " private " ,
2016-04-21 18:20:09 +02:00
" content " : " Test message " ,
" client " : " test suite " ,
2017-05-25 02:08:35 +02:00
" to " : self . example_email ( " othello " ) } )
2018-08-10 00:58:31 +02:00
self . assert_json_error_contains ( result , " Account is deactivated " , status_code = 400 )
2016-04-21 18:20:09 +02:00
2017-12-14 19:02:31 +01:00
result = self . api_post ( self . example_email ( " hamlet " ) ,
" /api/v1/messages " , { " type " : " private " ,
" content " : " Test message " ,
" client " : " test suite " ,
" to " : self . example_email ( " othello " ) } )
2018-08-10 00:58:31 +02:00
self . assert_json_error_contains ( result , " Account is deactivated " , status_code = 401 )
2016-04-21 18:20:09 +02:00
2017-11-05 10:51:25 +01:00
def test_fetch_api_key_deactivated_user ( self ) - > None :
2016-04-21 18:20:09 +02:00
"""
authenticated_json_view views fail with a deactivated user
"""
2017-05-07 19:39:30 +02:00
user_profile = self . example_user ( ' hamlet ' )
email = user_profile . email
2016-04-21 18:20:09 +02:00
test_password = " abcd1234 "
user_profile . set_password ( test_password )
2016-11-05 06:56:11 +01:00
user_profile . save ( )
2016-04-21 18:20:09 +02:00
2016-11-05 06:56:11 +01:00
self . login ( email , password = test_password )
2016-04-21 18:20:09 +02:00
user_profile . is_active = False
user_profile . save ( )
2016-07-28 00:30:22 +02:00
result = self . client_post ( " /json/fetch_api_key " , { " password " : test_password } )
2018-08-10 00:58:31 +02:00
self . assert_json_error_contains ( result , " Account is deactivated " , status_code = 400 )
2016-04-21 18:20:09 +02:00
2017-11-05 10:51:25 +01:00
def test_login_deactivated_user ( self ) - > None :
2016-04-21 18:20:09 +02:00
"""
logging in fails with an inactive user
"""
2017-05-07 19:39:30 +02:00
user_profile = self . example_user ( ' hamlet ' )
2016-04-21 18:20:09 +02:00
do_deactivate_user ( user_profile )
2017-05-25 01:40:26 +02:00
result = self . login_with_return ( self . example_email ( " hamlet " ) )
2017-06-19 13:36:48 +02:00
self . assert_in_response (
2017-11-20 07:23:55 +01:00
" Your account is no longer active. " ,
2017-06-19 13:36:48 +02:00
result )
2016-04-21 18:20:09 +02:00
2017-11-05 10:51:25 +01:00
def test_login_deactivated_mirror_dummy ( self ) - > None :
2017-06-27 13:10:16 +02:00
"""
logging in fails with an inactive user
"""
user_profile = self . example_user ( ' hamlet ' )
user_profile . is_mirror_dummy = True
user_profile . save ( )
password = initial_password ( user_profile . email )
request = mock . MagicMock ( )
2017-08-29 07:40:56 +02:00
request . get_host . return_value = ' zulip.testserver '
2017-06-27 13:10:16 +02:00
# Test a mirror-dummy active user.
form = OurAuthenticationForm ( request ,
data = { ' username ' : user_profile . email ,
' password ' : password } )
with self . settings ( AUTHENTICATION_BACKENDS = ( ' zproject.backends.EmailAuthBackend ' , ) ) :
self . assertTrue ( form . is_valid ( ) )
# Test a mirror-dummy deactivated user.
do_deactivate_user ( user_profile )
user_profile . save ( )
form = OurAuthenticationForm ( request ,
data = { ' username ' : user_profile . email ,
' password ' : password } )
with self . settings ( AUTHENTICATION_BACKENDS = ( ' zproject.backends.EmailAuthBackend ' , ) ) :
self . assertFalse ( form . is_valid ( ) )
self . assertIn ( " Please enter a correct email " , str ( form . errors ) )
# Test a non-mirror-dummy deactivated user.
user_profile . is_mirror_dummy = False
user_profile . save ( )
form = OurAuthenticationForm ( request ,
data = { ' username ' : user_profile . email ,
' password ' : password } )
with self . settings ( AUTHENTICATION_BACKENDS = ( ' zproject.backends.EmailAuthBackend ' , ) ) :
self . assertFalse ( form . is_valid ( ) )
2017-11-20 07:23:55 +01:00
self . assertIn ( " Your account is no longer active " , str ( form . errors ) )
2017-06-27 13:10:16 +02:00
2017-11-05 10:51:25 +01:00
def test_webhook_deactivated_user ( self ) - > None :
2016-04-21 18:20:09 +02:00
"""
Deactivated users can ' t use webhooks
"""
2017-05-07 19:39:30 +02:00
user_profile = self . example_user ( ' hamlet ' )
2016-04-21 18:20:09 +02:00
do_deactivate_user ( user_profile )
2018-08-01 10:53:40 +02:00
api_key = get_api_key ( user_profile )
url = " /api/v1/external/jira?api_key= %s &stream=jira_custom " % ( api_key , )
2018-04-20 03:57:21 +02:00
data = self . webhook_fixture_data ( ' jira ' , ' created_v2 ' )
2016-07-28 00:30:22 +02:00
result = self . client_post ( url , data ,
2016-04-21 18:20:09 +02:00
content_type = " application/json " )
2018-08-10 00:58:31 +02:00
self . assert_json_error_contains ( result , " Account is deactivated " , status_code = 400 )
2016-05-18 20:35:35 +02:00
2017-08-22 19:01:17 +02:00
class TestIncomingWebhookBot ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def setUp ( self ) - > None :
2019-10-19 20:47:00 +02:00
super ( ) . setUp ( )
2017-08-22 19:01:17 +02:00
zulip_realm = get_realm ( ' zulip ' )
self . webhook_bot = get_user ( ' webhook-bot@zulip.com ' , zulip_realm )
2017-11-05 10:51:25 +01:00
def test_webhook_bot_permissions ( self ) - > None :
2017-12-14 19:02:31 +01:00
result = self . api_post ( " webhook-bot@zulip.com " ,
" /api/v1/messages " , { " type " : " private " ,
" content " : " Test message " ,
" client " : " test suite " ,
" to " : self . example_email ( " othello " ) } )
2017-08-22 19:01:17 +02:00
self . assert_json_success ( result )
post_params = { " anchor " : 1 , " num_before " : 1 , " num_after " : 1 }
2017-12-14 19:02:31 +01:00
result = self . api_get ( " webhook-bot@zulip.com " , " /api/v1/messages " , dict ( post_params ) )
2017-08-22 19:01:17 +02:00
self . assert_json_error ( result , ' This API is not available to incoming webhook bots. ' ,
status_code = 401 )
2016-08-23 02:08:42 +02:00
class TestValidateApiKey ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def setUp ( self ) - > None :
2019-10-19 20:47:00 +02:00
super ( ) . setUp ( )
2017-05-23 20:57:59 +02:00
zulip_realm = get_realm ( ' zulip ' )
self . webhook_bot = get_user ( ' webhook-bot@zulip.com ' , zulip_realm )
self . default_bot = get_user ( ' default-bot@zulip.com ' , zulip_realm )
2016-05-18 20:35:35 +02:00
2019-12-16 06:27:34 +01:00
def test_has_api_key_format ( self ) - > None :
self . assertFalse ( has_api_key_format ( " TooShort " ) )
# Has an invalid character:
self . assertFalse ( has_api_key_format ( " 32LONGXXXXXXXXXXXXXXXXXXXXXXXXX- " ) )
# Too long:
self . assertFalse ( has_api_key_format ( " 33LONGXXXXXXXXXXXXXXXXXXXXXXXXXXX " ) )
self . assertTrue ( has_api_key_format ( " VIzRVw2CspUOnEm9Yu5vQiQtJNkvETkp " ) )
for i in range ( 0 , 10 ) :
self . assertTrue ( has_api_key_format ( generate_api_key ( ) ) )
2017-11-05 10:51:25 +01:00
def test_validate_api_key_if_profile_does_not_exist ( self ) - > None :
2016-05-18 20:35:35 +02:00
with self . assertRaises ( JsonableError ) :
2019-12-16 08:12:39 +01:00
validate_api_key ( HostRequestMock ( ) , ' email@doesnotexist.com ' , ' VIzRVw2CspUOnEm9Yu5vQiQtJNkvETkp ' )
2016-05-18 20:35:35 +02:00
2017-11-05 10:51:25 +01:00
def test_validate_api_key_if_api_key_does_not_match_profile_api_key ( self ) - > None :
2019-12-16 08:12:39 +01:00
with self . assertRaises ( InvalidAPIKeyFormatError ) :
2016-09-28 06:13:43 +02:00
validate_api_key ( HostRequestMock ( ) , self . webhook_bot . email , ' not_32_length ' )
2016-05-18 20:35:35 +02:00
2019-12-16 08:12:39 +01:00
with self . assertRaises ( InvalidAPIKeyError ) :
2018-07-29 18:04:40 +02:00
# We use default_bot's key but webhook_bot's email address to test
# the logic when an API key is passed and it doesn't belong to the
# user whose email address has been provided.
2018-08-01 10:53:40 +02:00
api_key = get_api_key ( self . default_bot )
validate_api_key ( HostRequestMock ( ) , self . webhook_bot . email , api_key )
2016-05-18 20:35:35 +02:00
2017-11-05 10:51:25 +01:00
def test_validate_api_key_if_profile_is_not_active ( self ) - > None :
2016-05-18 20:35:35 +02:00
self . _change_is_active_field ( self . default_bot , False )
with self . assertRaises ( JsonableError ) :
2018-08-01 10:53:40 +02:00
api_key = get_api_key ( self . default_bot )
validate_api_key ( HostRequestMock ( ) , self . default_bot . email , api_key )
2016-05-18 20:35:35 +02:00
self . _change_is_active_field ( self . default_bot , True )
2017-11-05 10:51:25 +01:00
def test_validate_api_key_if_profile_is_incoming_webhook_and_is_webhook_is_unset ( self ) - > None :
2016-05-18 20:35:35 +02:00
with self . assertRaises ( JsonableError ) :
2018-08-01 10:53:40 +02:00
api_key = get_api_key ( self . webhook_bot )
validate_api_key ( HostRequestMock ( ) , self . webhook_bot . email , api_key )
2016-05-18 20:35:35 +02:00
2017-11-05 10:51:25 +01:00
def test_validate_api_key_if_profile_is_incoming_webhook_and_is_webhook_is_set ( self ) - > None :
2018-08-01 10:53:40 +02:00
api_key = get_api_key ( self . webhook_bot )
2017-08-29 07:40:56 +02:00
profile = validate_api_key ( HostRequestMock ( host = " zulip.testserver " ) ,
2018-08-01 10:53:40 +02:00
self . webhook_bot . email , api_key ,
2017-08-29 07:40:56 +02:00
is_webhook = True )
2017-10-06 13:15:59 +02:00
self . assertEqual ( profile . id , self . webhook_bot . id )
2016-05-18 20:35:35 +02:00
2018-01-23 12:36:36 +01:00
def test_validate_api_key_if_email_is_case_insensitive ( self ) - > None :
2018-08-01 10:53:40 +02:00
api_key = get_api_key ( self . default_bot )
profile = validate_api_key ( HostRequestMock ( host = " zulip.testserver " ) , self . default_bot . email . upper ( ) , api_key )
2018-01-23 12:36:36 +01:00
self . assertEqual ( profile . id , self . default_bot . id )
2017-11-05 10:51:25 +01:00
def test_valid_api_key_if_user_is_on_wrong_subdomain ( self ) - > None :
2017-10-02 22:37:20 +02:00
with self . settings ( RUNNING_INSIDE_TORNADO = False ) :
2018-08-01 10:53:40 +02:00
api_key = get_api_key ( self . default_bot )
2017-10-02 22:37:20 +02:00
with mock . patch ( ' logging.warning ' ) as mock_warning :
with self . assertRaisesRegex ( JsonableError ,
" Account is not associated with this subdomain " ) :
validate_api_key ( HostRequestMock ( host = settings . EXTERNAL_HOST ) ,
2018-08-01 10:53:40 +02:00
self . default_bot . email , api_key )
2017-10-02 22:37:20 +02:00
mock_warning . assert_called_with (
" User {} ( {} ) attempted to access API on wrong "
" subdomain ( {} ) " . format ( self . default_bot . email , ' zulip ' , ' ' ) )
with mock . patch ( ' logging.warning ' ) as mock_warning :
with self . assertRaisesRegex ( JsonableError ,
" Account is not associated with this subdomain " ) :
validate_api_key ( HostRequestMock ( host = ' acme. ' + settings . EXTERNAL_HOST ) ,
2018-08-01 10:53:40 +02:00
self . default_bot . email , api_key )
2017-10-02 22:37:20 +02:00
mock_warning . assert_called_with (
" User {} ( {} ) attempted to access API on wrong "
" subdomain ( {} ) " . format ( self . default_bot . email , ' zulip ' , ' acme ' ) )
2016-10-12 10:43:20 +02:00
2017-11-05 10:51:25 +01:00
def _change_is_active_field ( self , profile : UserProfile , value : bool ) - > None :
2016-05-18 20:35:35 +02:00
profile . is_active = value
profile . save ( )
2016-07-09 12:02:24 +02:00
class TestInternalNotifyView ( TestCase ) :
BORING_RESULT = ' boring '
2017-11-05 11:49:43 +01:00
class Request :
2017-11-05 10:51:25 +01:00
def __init__ ( self , POST : Dict [ str , Any ] , META : Dict [ str , Any ] ) - > None :
2016-07-09 12:02:24 +02:00
self . POST = POST
self . META = META
self . method = ' POST '
2017-11-05 10:51:25 +01:00
def internal_notify ( self , is_tornado : bool , req : HttpRequest ) - > HttpResponse :
2016-07-09 12:02:24 +02:00
boring_view = lambda req : self . BORING_RESULT
2017-04-18 18:56:19 +02:00
return internal_notify_view ( is_tornado ) ( boring_view ) ( req )
2016-07-09 12:02:24 +02:00
2017-11-05 10:51:25 +01:00
def test_valid_internal_requests ( self ) - > None :
2016-07-09 12:02:24 +02:00
secret = ' random '
req = self . Request (
POST = dict ( secret = secret ) ,
META = dict ( REMOTE_ADDR = ' 127.0.0.1 ' ) ,
2017-08-06 02:44:55 +02:00
) # type: HttpRequest
2016-07-09 12:02:24 +02:00
2017-04-18 18:56:19 +02:00
with self . settings ( SHARED_SECRET = secret ) :
self . assertTrue ( authenticate_notify ( req ) )
self . assertEqual ( self . internal_notify ( False , req ) , self . BORING_RESULT )
2020-03-09 11:39:20 +01:00
self . assertEqual ( req . _requestor_for_logs , ' internal ' )
2017-04-18 18:56:19 +02:00
with self . assertRaises ( RuntimeError ) :
self . internal_notify ( True , req )
2016-07-09 12:02:24 +02:00
req . _tornado_handler = ' set '
with self . settings ( SHARED_SECRET = secret ) :
self . assertTrue ( authenticate_notify ( req ) )
2017-04-18 18:56:19 +02:00
self . assertEqual ( self . internal_notify ( True , req ) , self . BORING_RESULT )
2020-03-09 11:39:20 +01:00
self . assertEqual ( req . _requestor_for_logs , ' internal ' )
2016-07-09 12:02:24 +02:00
2017-04-18 18:56:19 +02:00
with self . assertRaises ( RuntimeError ) :
self . internal_notify ( False , req )
2017-11-05 10:51:25 +01:00
def test_internal_requests_with_broken_secret ( self ) - > None :
2016-07-09 12:02:24 +02:00
secret = ' random '
req = self . Request (
POST = dict ( secret = secret ) ,
META = dict ( REMOTE_ADDR = ' 127.0.0.1 ' ) ,
2017-01-24 06:34:26 +01:00
)
2016-07-09 12:02:24 +02:00
with self . settings ( SHARED_SECRET = ' broken ' ) :
self . assertFalse ( authenticate_notify ( req ) )
2017-04-18 18:56:19 +02:00
self . assertEqual ( self . internal_notify ( True , req ) . status_code , 403 )
2016-07-09 12:02:24 +02:00
2017-11-05 10:51:25 +01:00
def test_external_requests ( self ) - > None :
2016-07-09 12:02:24 +02:00
secret = ' random '
req = self . Request (
POST = dict ( secret = secret ) ,
META = dict ( REMOTE_ADDR = ' 3.3.3.3 ' ) ,
2017-01-24 06:34:26 +01:00
)
2016-07-09 12:02:24 +02:00
with self . settings ( SHARED_SECRET = secret ) :
self . assertFalse ( authenticate_notify ( req ) )
2017-04-18 18:56:19 +02:00
self . assertEqual ( self . internal_notify ( True , req ) . status_code , 403 )
2016-05-18 20:35:35 +02:00
2017-11-05 10:51:25 +01:00
def test_is_local_address ( self ) - > None :
2016-07-09 20:37:09 +02:00
self . assertTrue ( is_local_addr ( ' 127.0.0.1 ' ) )
self . assertTrue ( is_local_addr ( ' ::1 ' ) )
self . assertFalse ( is_local_addr ( ' 42.43.44.45 ' ) )
2017-04-15 20:51:51 +02:00
class TestHumanUsersOnlyDecorator ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_human_only_endpoints ( self ) - > None :
2017-04-16 22:10:56 +02:00
post_endpoints = [
2017-06-19 22:09:35 +02:00
" /api/v1/users/me/apns_device_token " ,
" /api/v1/users/me/android_gcm_reg_id " ,
2017-10-28 00:16:13 +02:00
" /api/v1/users/me/enter-sends " ,
2017-07-31 20:28:15 +02:00
" /api/v1/users/me/hotspots " ,
2017-10-28 00:16:13 +02:00
" /api/v1/users/me/presence " ,
" /api/v1/users/me/tutorial_status " ,
" /api/v1/report/send_times " ,
" /api/v1/report/narrow_times " ,
" /api/v1/report/unnarrow_times " ,
2017-04-15 20:51:51 +02:00
]
2017-04-16 22:10:56 +02:00
for endpoint in post_endpoints :
2017-12-14 19:02:31 +01:00
result = self . api_post ( ' default-bot@zulip.com ' , endpoint )
2017-04-15 20:51:51 +02:00
self . assert_json_error ( result , " This endpoint does not accept bot requests. " )
2017-04-16 22:10:56 +02:00
patch_endpoints = [
2017-07-31 20:44:52 +02:00
" /api/v1/settings " ,
2017-04-16 22:10:56 +02:00
" /api/v1/settings/display " ,
" /api/v1/settings/notifications " ,
2017-07-31 20:30:08 +02:00
" /api/v1/users/me/profile_data "
2017-04-16 22:10:56 +02:00
]
for endpoint in patch_endpoints :
2017-12-14 19:02:31 +01:00
result = self . api_patch ( ' default-bot@zulip.com ' , endpoint )
2017-04-16 22:10:56 +02:00
self . assert_json_error ( result , " This endpoint does not accept bot requests. " )
2017-06-19 22:09:35 +02:00
delete_endpoints = [
" /api/v1/users/me/apns_device_token " ,
" /api/v1/users/me/android_gcm_reg_id " ,
]
for endpoint in delete_endpoints :
2017-12-14 19:02:31 +01:00
result = self . api_delete ( ' default-bot@zulip.com ' , endpoint )
2017-06-19 22:09:35 +02:00
self . assert_json_error ( result , " This endpoint does not accept bot requests. " )
2016-08-23 02:08:42 +02:00
class TestAuthenticatedJsonPostViewDecorator ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_authenticated_json_post_view_if_everything_is_correct ( self ) - > None :
2017-05-23 20:57:59 +02:00
user_email = self . example_email ( ' hamlet ' )
user_realm = get_realm ( ' zulip ' )
self . _login ( user_email , user_realm )
2016-05-18 20:35:35 +02:00
response = self . _do_test ( user_email )
self . assertEqual ( response . status_code , 200 )
2018-04-26 07:28:31 +02:00
def test_authenticated_json_post_view_with_get_request ( self ) - > None :
user_email = self . example_email ( ' hamlet ' )
user_realm = get_realm ( ' zulip ' )
self . _login ( user_email , user_realm )
with mock . patch ( ' logging.warning ' ) as mock_warning :
result = self . client_get ( r ' /json/subscriptions/exists ' , { ' stream ' : ' Verona ' } )
self . assertEqual ( result . status_code , 405 )
mock_warning . assert_called_once ( ) # Check we logged the Mock Not Allowed
self . assertEqual ( mock_warning . call_args_list [ 0 ] [ 0 ] ,
( ' Method Not Allowed ( %s ): %s ' , ' GET ' , ' /json/subscriptions/exists ' ) )
2017-11-05 10:51:25 +01:00
def test_authenticated_json_post_view_if_subdomain_is_invalid ( self ) - > None :
2017-05-23 20:57:59 +02:00
user_email = self . example_email ( ' hamlet ' )
user_realm = get_realm ( ' zulip ' )
self . _login ( user_email , user_realm )
2017-10-02 22:37:20 +02:00
with mock . patch ( ' logging.warning ' ) as mock_warning , \
mock . patch ( ' zerver.decorator.get_subdomain ' , return_value = ' ' ) :
self . assert_json_error_contains ( self . _do_test ( user_email ) ,
" Account is not associated with this "
" subdomain " )
mock_warning . assert_called_with (
" User {} ( {} ) attempted to access API on wrong "
" subdomain ( {} ) " . format ( user_email , ' zulip ' , ' ' ) )
with mock . patch ( ' logging.warning ' ) as mock_warning , \
mock . patch ( ' zerver.decorator.get_subdomain ' , return_value = ' acme ' ) :
self . assert_json_error_contains ( self . _do_test ( user_email ) ,
" Account is not associated with this "
" subdomain " )
mock_warning . assert_called_with (
" User {} ( {} ) attempted to access API on wrong "
" subdomain ( {} ) " . format ( user_email , ' zulip ' , ' acme ' ) )
2016-10-12 10:43:20 +02:00
2017-11-05 10:51:25 +01:00
def test_authenticated_json_post_view_if_user_is_incoming_webhook ( self ) - > None :
2016-05-18 20:35:35 +02:00
user_email = ' webhook-bot@zulip.com '
2017-05-23 20:57:59 +02:00
user_realm = get_realm ( ' zulip ' )
self . _login ( user_email , user_realm , password = " test " ) # we set a password because user is a bot
2016-05-18 20:35:35 +02:00
self . assert_json_error_contains ( self . _do_test ( user_email ) , " Webhook bots can only access webhooks " )
2017-11-05 10:51:25 +01:00
def test_authenticated_json_post_view_if_user_is_not_active ( self ) - > None :
2017-05-23 20:57:59 +02:00
user_email = self . example_email ( ' hamlet ' )
user_realm = get_realm ( ' zulip ' )
self . _login ( user_email , user_realm , password = " test " )
2016-11-05 06:56:11 +01:00
# Get user_profile after _login so that we have the latest data.
2017-05-23 20:57:59 +02:00
user_profile = get_user ( user_email , user_realm )
2016-05-18 20:35:35 +02:00
# we deactivate user manually because do_deactivate_user removes user session
user_profile . is_active = False
user_profile . save ( )
2018-08-10 00:58:31 +02:00
self . assert_json_error_contains ( self . _do_test ( user_email ) , " Account is deactivated " )
2016-05-18 20:35:35 +02:00
do_reactivate_user ( user_profile )
2017-11-05 10:51:25 +01:00
def test_authenticated_json_post_view_if_user_realm_is_deactivated ( self ) - > None :
2017-05-23 20:57:59 +02:00
user_email = self . example_email ( ' hamlet ' )
user_realm = get_realm ( ' zulip ' )
user_profile = get_user ( user_email , user_realm )
self . _login ( user_email , user_realm )
2016-05-18 20:35:35 +02:00
# we deactivate user's realm manually because do_deactivate_user removes user session
user_profile . realm . deactivated = True
user_profile . realm . save ( )
2018-03-17 00:51:11 +01:00
self . assert_json_error_contains ( self . _do_test ( user_email ) , " This organization has been deactivated " )
2016-05-18 20:35:35 +02:00
do_reactivate_realm ( user_profile . realm )
2018-05-11 01:39:38 +02:00
def _do_test ( self , user_email : str ) - > HttpResponse :
2018-01-05 21:25:33 +01:00
stream_name = " stream name "
2020-03-09 21:41:26 +01:00
user = get_user ( user_email , get_realm ( ' zulip ' ) )
self . common_subscribe_to_streams ( user , [ stream_name ] )
2018-01-05 21:25:33 +01:00
data = { " password " : initial_password ( user_email ) , " stream " : stream_name }
return self . client_post ( r ' /json/subscriptions/exists ' , data )
2016-05-18 20:35:35 +02:00
2018-05-11 01:39:38 +02:00
def _login ( self , user_email : str , user_realm : Realm , password : str = None ) - > None :
2016-05-18 20:35:35 +02:00
if password :
2017-05-23 20:57:59 +02:00
user_profile = get_user ( user_email , user_realm )
2016-05-18 20:35:35 +02:00
user_profile . set_password ( password )
user_profile . save ( )
self . login ( user_email , password )
2016-10-12 10:43:20 +02:00
class TestAuthenticatedJsonViewDecorator ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_authenticated_json_view_if_subdomain_is_invalid ( self ) - > None :
2017-05-25 01:40:26 +02:00
user_email = self . example_email ( " hamlet " )
2017-03-05 08:34:28 +01:00
self . login ( user_email )
2016-10-12 10:43:20 +02:00
2017-10-02 22:37:20 +02:00
with mock . patch ( ' logging.warning ' ) as mock_warning , \
mock . patch ( ' zerver.decorator.get_subdomain ' , return_value = ' ' ) :
self . assert_json_error_contains ( self . _do_test ( str ( user_email ) ) ,
" Account is not associated with this "
" subdomain " )
mock_warning . assert_called_with (
" User {} ( {} ) attempted to access API on wrong "
" subdomain ( {} ) " . format ( user_email , ' zulip ' , ' ' ) )
with mock . patch ( ' logging.warning ' ) as mock_warning , \
mock . patch ( ' zerver.decorator.get_subdomain ' , return_value = ' acme ' ) :
self . assert_json_error_contains ( self . _do_test ( str ( user_email ) ) ,
" Account is not associated with this "
" subdomain " )
mock_warning . assert_called_with (
" User {} ( {} ) attempted to access API on wrong "
" subdomain ( {} ) " . format ( user_email , ' zulip ' , ' acme ' ) )
2016-10-12 10:43:20 +02:00
2017-11-05 10:51:25 +01:00
def _do_test ( self , user_email : str ) - > HttpResponse :
2017-10-09 22:48:17 +02:00
data = { " password " : initial_password ( user_email ) }
2018-01-05 21:25:33 +01:00
return self . client_post ( r ' /accounts/webathena_kerberos_login/ ' , data )
2016-10-12 10:43:20 +02:00
class TestZulipLoginRequiredDecorator ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_zulip_login_required_if_subdomain_is_invalid ( self ) - > None :
2017-05-25 01:40:26 +02:00
user_email = self . example_email ( " hamlet " )
2016-10-12 10:43:20 +02:00
self . login ( user_email )
2017-10-02 22:37:20 +02:00
with mock . patch ( ' zerver.decorator.get_subdomain ' , return_value = ' zulip ' ) :
result = self . client_get ( ' /accounts/accept_terms/ ' )
self . assertEqual ( result . status_code , 200 )
2016-10-12 10:43:20 +02:00
2017-10-02 22:37:20 +02:00
with mock . patch ( ' zerver.decorator.get_subdomain ' , return_value = ' ' ) :
result = self . client_get ( ' /accounts/accept_terms/ ' )
self . assertEqual ( result . status_code , 302 )
2016-10-12 10:43:20 +02:00
2017-10-02 22:37:20 +02:00
with mock . patch ( ' zerver.decorator.get_subdomain ' , return_value = ' acme ' ) :
result = self . client_get ( ' /accounts/accept_terms/ ' )
self . assertEqual ( result . status_code , 302 )
2016-11-15 17:20:22 +01:00
2017-07-12 10:16:02 +02:00
def test_2fa_failure ( self ) - > None :
@zulip_login_required
def test_view ( request : HttpRequest ) - > HttpResponse :
return HttpResponse ( ' Success ' )
request = HttpRequest ( )
request . META [ ' SERVER_NAME ' ] = ' localhost '
request . META [ ' SERVER_PORT ' ] = 80
request . META [ ' PATH_INFO ' ] = ' '
request . user = hamlet = self . example_user ( ' hamlet ' )
request . user . is_verified = lambda : False
self . login ( hamlet . email )
request . session = self . client . session
request . get_host = lambda : ' zulip.testserver '
response = test_view ( request )
content = getattr ( response , ' content ' )
self . assertEqual ( content . decode ( ) , ' Success ' )
with self . settings ( TWO_FACTOR_AUTHENTICATION_ENABLED = True ) :
request = HttpRequest ( )
request . META [ ' SERVER_NAME ' ] = ' localhost '
request . META [ ' SERVER_PORT ' ] = 80
request . META [ ' PATH_INFO ' ] = ' '
request . user = hamlet = self . example_user ( ' hamlet ' )
request . user . is_verified = lambda : False
self . login ( hamlet . email )
request . session = self . client . session
request . get_host = lambda : ' zulip.testserver '
self . create_default_device ( request . user )
response = test_view ( request )
status_code = getattr ( response , ' status_code ' )
self . assertEqual ( status_code , 302 )
url = getattr ( response , ' url ' )
response_url = url . split ( " ? " ) [ 0 ]
self . assertEqual ( response_url , settings . HOME_NOT_LOGGED_IN )
def test_2fa_success ( self ) - > None :
@zulip_login_required
def test_view ( request : HttpRequest ) - > HttpResponse :
return HttpResponse ( ' Success ' )
with self . settings ( TWO_FACTOR_AUTHENTICATION_ENABLED = True ) :
request = HttpRequest ( )
request . META [ ' SERVER_NAME ' ] = ' localhost '
request . META [ ' SERVER_PORT ' ] = 80
request . META [ ' PATH_INFO ' ] = ' '
request . user = hamlet = self . example_user ( ' hamlet ' )
request . user . is_verified = lambda : True
self . login ( hamlet . email )
request . session = self . client . session
request . get_host = lambda : ' zulip.testserver '
self . create_default_device ( request . user )
response = test_view ( request )
content = getattr ( response , ' content ' )
self . assertEqual ( content . decode ( ) , ' Success ' )
2018-05-04 19:14:29 +02:00
class TestRequireDecorators ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_require_server_admin_decorator ( self ) - > None :
2017-05-23 20:57:59 +02:00
user_email = self . example_email ( ' hamlet ' )
user_realm = get_realm ( ' zulip ' )
2016-12-14 06:02:50 +01:00
self . login ( user_email )
result = self . client_get ( ' /activity ' )
self . assertEqual ( result . status_code , 302 )
2017-05-23 20:57:59 +02:00
user_profile = get_user ( user_email , user_realm )
2016-12-14 06:02:50 +01:00
user_profile . is_staff = True
user_profile . save ( )
result = self . client_get ( ' /activity ' )
self . assertEqual ( result . status_code , 200 )
2018-05-04 19:14:29 +02:00
def test_require_non_guest_user_decorator ( self ) - > None :
guest_user = self . example_user ( ' polonius ' )
self . login ( guest_user . email )
2020-03-09 21:41:26 +01:00
result = self . common_subscribe_to_streams ( guest_user , [ " Denmark " ] )
2018-05-04 19:14:29 +02:00
self . assert_json_error ( result , " Not allowed for guest users " )
2019-06-18 16:43:22 +02:00
def test_require_member_or_admin_decorator ( self ) - > None :
2018-05-04 19:14:29 +02:00
result = self . api_get ( " outgoing-webhook@zulip.com " , ' /api/v1/bots ' )
self . assert_json_error ( result , " This endpoint does not accept bot requests. " )
guest_user = self . example_user ( ' polonius ' )
self . login ( guest_user . email )
result = self . client_get ( ' /json/bots ' )
self . assert_json_error ( result , " Not allowed for guest users " )
2016-11-15 17:20:22 +01:00
class ReturnSuccessOnHeadRequestDecorator ( ZulipTestCase ) :
2017-11-17 07:00:53 +01:00
def test_returns_200_if_request_method_is_head ( self ) - > None :
2017-11-05 11:49:43 +01:00
class HeadRequest :
2016-11-15 17:20:22 +01:00
method = ' HEAD '
request = HeadRequest ( )
@return_success_on_head_request
2017-11-05 10:51:25 +01:00
def test_function ( request : HttpRequest ) - > HttpResponse :
2017-03-05 08:34:28 +01:00
return json_response ( msg = u ' from_test_function ' ) # nocoverage. isn't meant to be called
2016-11-15 17:20:22 +01:00
response = test_function ( request )
self . assert_json_success ( response )
self . assertNotEqual ( ujson . loads ( response . content ) . get ( ' msg ' ) , u ' from_test_function ' )
2017-11-17 07:00:53 +01:00
def test_returns_normal_response_if_request_method_is_not_head ( self ) - > None :
2019-01-31 14:32:37 +01:00
class HeadRequest :
method = ' POST '
2016-11-15 17:20:22 +01:00
2019-01-31 14:32:37 +01:00
request = HeadRequest ( )
2016-11-15 17:20:22 +01:00
2019-01-31 14:32:37 +01:00
@return_success_on_head_request
def test_function ( request : HttpRequest ) - > HttpResponse :
return json_response ( msg = u ' from_test_function ' )
2016-11-15 17:20:22 +01:00
2019-01-31 14:32:37 +01:00
response = test_function ( request )
self . assertEqual ( ujson . loads ( response . content ) . get ( ' msg ' ) , u ' from_test_function ' )
2017-03-05 09:26:07 +01:00
class RestAPITest ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_method_not_allowed ( self ) - > None :
2017-05-25 01:40:26 +02:00
self . login ( self . example_email ( " hamlet " ) )
2017-03-05 09:26:07 +01:00
result = self . client_patch ( ' /json/users ' )
self . assertEqual ( result . status_code , 405 )
self . assert_in_response ( ' Method Not Allowed ' , result )
2017-03-05 09:31:17 +01:00
2017-11-05 10:51:25 +01:00
def test_options_method ( self ) - > None :
2017-05-25 01:40:26 +02:00
self . login ( self . example_email ( " hamlet " ) )
2017-03-05 09:31:17 +01:00
result = self . client_options ( ' /json/users ' )
self . assertEqual ( result . status_code , 204 )
2019-08-12 05:44:35 +02:00
self . assertEqual ( str ( result [ ' Allow ' ] ) , ' GET, HEAD, POST ' )
2017-03-05 09:31:17 +01:00
result = self . client_options ( ' /json/streams/15 ' )
self . assertEqual ( result . status_code , 204 )
self . assertEqual ( str ( result [ ' Allow ' ] ) , ' DELETE, PATCH ' )
2017-03-05 09:39:36 +01:00
2017-11-05 10:51:25 +01:00
def test_http_accept_redirect ( self ) - > None :
2017-03-05 09:39:36 +01:00
result = self . client_get ( ' /json/users ' ,
HTTP_ACCEPT = ' text/html ' )
self . assertEqual ( result . status_code , 302 )
self . assertTrue ( result [ " Location " ] . endswith ( " /login/?next=/json/users " ) )
2017-09-16 08:24:17 +02:00
2017-10-31 00:31:47 +01:00
class CacheTestCase ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_cachify_basics ( self ) - > None :
2017-10-31 00:31:47 +01:00
@cachify
2017-11-05 10:51:25 +01:00
def add ( w : Any , x : Any , y : Any , z : Any ) - > Any :
2017-10-31 00:31:47 +01:00
return w + x + y + z
for i in range ( 2 ) :
self . assertEqual ( add ( 1 , 2 , 4 , 8 ) , 15 )
self . assertEqual ( add ( ' a ' , ' b ' , ' c ' , ' d ' ) , ' abcd ' )
2017-11-05 10:51:25 +01:00
def test_cachify_is_per_call ( self ) - > None :
2017-10-31 00:31:47 +01:00
2018-05-11 01:39:38 +02:00
def test_greetings ( greeting : str ) - > Tuple [ List [ str ] , List [ str ] ] :
2017-10-31 00:31:47 +01:00
2018-05-11 01:39:38 +02:00
result_log = [ ] # type: List[str]
work_log = [ ] # type: List[str]
2017-10-31 00:31:47 +01:00
@cachify
2018-05-11 01:39:38 +02:00
def greet ( first_name : str , last_name : str ) - > str :
2017-10-31 00:31:47 +01:00
msg = ' %s %s %s ' % ( greeting , first_name , last_name )
work_log . append ( msg )
return msg
result_log . append ( greet ( ' alice ' , ' smith ' ) )
result_log . append ( greet ( ' bob ' , ' barker ' ) )
result_log . append ( greet ( ' alice ' , ' smith ' ) )
result_log . append ( greet ( ' cal ' , ' johnson ' ) )
return ( work_log , result_log )
work_log , result_log = test_greetings ( ' hello ' )
self . assertEqual ( work_log , [
' hello alice smith ' ,
' hello bob barker ' ,
' hello cal johnson ' ,
] )
self . assertEqual ( result_log , [
' hello alice smith ' ,
' hello bob barker ' ,
' hello alice smith ' ,
' hello cal johnson ' ,
] )
work_log , result_log = test_greetings ( ' goodbye ' )
self . assertEqual ( work_log , [
' goodbye alice smith ' ,
' goodbye bob barker ' ,
' goodbye cal johnson ' ,
] )
self . assertEqual ( result_log , [
' goodbye alice smith ' ,
' goodbye bob barker ' ,
' goodbye alice smith ' ,
' goodbye cal johnson ' ,
] )
2017-09-16 08:24:17 +02:00
class TestUserAgentParsing ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_user_agent_parsing ( self ) - > None :
2017-09-16 08:24:17 +02:00
""" Test for our user agent parsing logic, using a large data set. """
user_agents_parsed = defaultdict ( int ) # type: Dict[str, int]
2018-04-19 20:17:24 +02:00
user_agents_path = os . path . join ( settings . DEPLOY_ROOT , " zerver/tests/fixtures/user_agents_unique " )
2017-09-16 08:24:17 +02:00
for line in open ( user_agents_path ) . readlines ( ) :
line = line . strip ( )
match = re . match ( ' ^(?P<count>[0-9]+) " (?P<user_agent>.*) " $ ' , line )
self . assertIsNotNone ( match )
groupdict = match . groupdict ( )
count = groupdict [ " count " ]
user_agent = groupdict [ " user_agent " ]
ret = parse_user_agent ( user_agent )
user_agents_parsed [ ret [ " name " ] ] + = int ( count )
2018-01-12 08:57:10 +01:00
class TestIgnoreUnhashableLRUCache ( ZulipTestCase ) :
def test_cache_hit ( self ) - > None :
@ignore_unhashable_lru_cache ( )
def f ( arg : Any ) - > Any :
return arg
def get_cache_info ( ) - > Tuple [ int , int , int ] :
info = getattr ( f , ' cache_info ' ) ( )
hits = getattr ( info , ' hits ' )
misses = getattr ( info , ' misses ' )
currsize = getattr ( info , ' currsize ' )
return hits , misses , currsize
def clear_cache ( ) - > None :
getattr ( f , ' cache_clear ' ) ( )
# Check hashable argument.
result = f ( 1 )
hits , misses , currsize = get_cache_info ( )
# First one should be a miss.
self . assertEqual ( hits , 0 )
self . assertEqual ( misses , 1 )
self . assertEqual ( currsize , 1 )
self . assertEqual ( result , 1 )
result = f ( 1 )
hits , misses , currsize = get_cache_info ( )
# Second one should be a hit.
self . assertEqual ( hits , 1 )
self . assertEqual ( misses , 1 )
self . assertEqual ( currsize , 1 )
self . assertEqual ( result , 1 )
# Check unhashable argument.
2019-04-18 04:35:14 +02:00
result = f ( { 1 : 2 } )
2018-01-12 08:57:10 +01:00
hits , misses , currsize = get_cache_info ( )
# Cache should not be used.
self . assertEqual ( hits , 1 )
self . assertEqual ( misses , 1 )
self . assertEqual ( currsize , 1 )
2019-04-18 04:35:14 +02:00
self . assertEqual ( result , { 1 : 2 } )
# Clear cache.
clear_cache ( )
hits , misses , currsize = get_cache_info ( )
self . assertEqual ( hits , 0 )
self . assertEqual ( misses , 0 )
self . assertEqual ( currsize , 0 )
def test_cache_hit_dict_args ( self ) - > None :
@ignore_unhashable_lru_cache ( )
@items_tuple_to_dict
def g ( arg : Any ) - > Any :
return arg
def get_cache_info ( ) - > Tuple [ int , int , int ] :
info = getattr ( g , ' cache_info ' ) ( )
hits = getattr ( info , ' hits ' )
misses = getattr ( info , ' misses ' )
currsize = getattr ( info , ' currsize ' )
return hits , misses , currsize
def clear_cache ( ) - > None :
getattr ( g , ' cache_clear ' ) ( )
# Not used as a decorator on the definition to allow defining
# get_cache_info and clear_cache
f = dict_to_items_tuple ( g )
# Check hashable argument.
result = f ( 1 )
hits , misses , currsize = get_cache_info ( )
# First one should be a miss.
self . assertEqual ( hits , 0 )
self . assertEqual ( misses , 1 )
self . assertEqual ( currsize , 1 )
self . assertEqual ( result , 1 )
result = f ( 1 )
hits , misses , currsize = get_cache_info ( )
# Second one should be a hit.
self . assertEqual ( hits , 1 )
self . assertEqual ( misses , 1 )
self . assertEqual ( currsize , 1 )
self . assertEqual ( result , 1 )
# Check dict argument.
result = f ( { 1 : 2 } )
hits , misses , currsize = get_cache_info ( )
# First one is a miss
self . assertEqual ( hits , 1 )
self . assertEqual ( misses , 2 )
self . assertEqual ( currsize , 2 )
self . assertEqual ( result , { 1 : 2 } )
result = f ( { 1 : 2 } )
hits , misses , currsize = get_cache_info ( )
# Second one should be a hit.
self . assertEqual ( hits , 2 )
self . assertEqual ( misses , 2 )
self . assertEqual ( currsize , 2 )
self . assertEqual ( result , { 1 : 2 } )
2018-01-12 08:57:10 +01:00
# Clear cache.
clear_cache ( )
hits , misses , currsize = get_cache_info ( )
self . assertEqual ( hits , 0 )
self . assertEqual ( misses , 0 )
self . assertEqual ( currsize , 0 )