2018-03-27 04:34:43 +02:00
import base64
2017-09-16 08:24:17 +02:00
import os
2020-06-11 00:54:34 +02:00
import re
2021-12-22 14:37:12 +01:00
import uuid
2017-09-16 08:24:17 +02:00
from collections import defaultdict
2022-08-01 20:46:23 +02:00
from typing import TYPE_CHECKING , Any , Callable , Dict , List , Optional , Sequence , Tuple , Union
2020-08-27 22:46:39 +02:00
from unittest import mock , skipUnless
2017-07-12 10:16:02 +02:00
2020-08-07 01:09:47 +02:00
import orjson
2016-10-12 10:43:20 +02:00
from django . conf import settings
2022-08-01 20:46:23 +02:00
from django . contrib . auth . models import AnonymousUser
2020-06-21 02:36:20 +02:00
from django . core . exceptions import ValidationError
2020-06-11 00:54:34 +02:00
from django . http import HttpRequest , HttpResponse
2020-08-27 17:20:54 +02:00
from django . utils . timezone import now as timezone_now
2016-05-18 20:35:35 +02:00
2022-04-14 23:58:15 +02:00
from zerver . actions . create_realm import do_create_realm
2022-04-14 23:53:15 +02:00
from zerver . actions . create_user import do_reactivate_user
2021-10-26 09:15:16 +02:00
from zerver . actions . realm_settings import do_deactivate_realm , do_reactivate_realm
from zerver . actions . user_settings import do_change_user_setting
2022-04-14 23:48:28 +02:00
from zerver . actions . users import change_user_is_active , do_deactivate_user
2016-07-09 07:47:07 +02:00
from zerver . decorator import (
2020-06-11 00:54:34 +02:00
authenticate_notify ,
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 ,
2020-06-11 00:54:34 +02:00
internal_notify_view ,
2023-02-14 22:45:00 +01:00
process_client ,
2022-08-01 20:46:23 +02:00
public_json_view ,
2020-05-07 13:19:54 +02:00
return_success_on_head_request ,
2020-06-11 00:54:34 +02:00
validate_api_key ,
2022-08-14 21:14:52 +02:00
web_public_view ,
2020-08-20 00:32:15 +02:00
webhook_view ,
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
zulip_login_required ,
2022-07-11 16:33:02 +02:00
zulip_otp_required_if_logged_in ,
2017-01-24 06:34:26 +01:00
)
2020-06-11 00:54:34 +02:00
from zerver . forms import OurAuthenticationForm
from zerver . lib . cache import dict_to_items_tuple , ignore_unhashable_lru_cache , items_tuple_to_dict
2020-08-19 22:14:40 +02:00
from zerver . lib . exceptions import (
2021-07-04 08:45:34 +02:00
AccessDeniedError ,
2020-08-19 22:14:40 +02:00
InvalidAPIKeyError ,
InvalidAPIKeyFormatError ,
2021-12-15 02:02:38 +01:00
InvalidJSONError ,
2020-08-19 22:14:40 +02:00
JsonableError ,
2022-11-17 09:30:48 +01:00
UnsupportedWebhookEventTypeError ,
2020-08-19 22:14:40 +02:00
)
2020-06-11 00:54:34 +02:00
from zerver . lib . initial_password import initial_password
2022-08-15 00:17:12 +02:00
from zerver . lib . rate_limiter import is_local_addr
2020-06-11 00:54:34 +02:00
from zerver . lib . request import (
REQ ,
2023-01-02 20:50:23 +01:00
RequestConfusingParamsError ,
2021-08-21 19:24:20 +02:00
RequestNotes ,
2020-06-11 00:54:34 +02:00
RequestVariableConversionError ,
RequestVariableMissingError ,
has_request_variables ,
)
2022-08-25 18:41:46 +02:00
from zerver . lib . response import MutableJsonResponse , json_response , json_success
2020-06-11 00:54:34 +02:00
from zerver . lib . test_classes import ZulipTestCase
2023-02-14 22:45:00 +01:00
from zerver . lib . test_helpers import HostRequestMock , dummy_handler , queries_captured
2021-07-16 22:11:10 +02:00
from zerver . lib . types import Validator
2020-06-11 00:54:34 +02:00
from zerver . lib . user_agent import parse_user_agent
from zerver . lib . users import get_api_key
from zerver . lib . utils import generate_api_key , has_api_key_format
2014-02-14 19:56:55 +01:00
from zerver . lib . validator import (
2020-06-11 00:54:34 +02:00
check_bool ,
check_capped_string ,
check_color ,
check_dict ,
check_dict_only ,
check_float ,
check_int ,
check_int_in ,
check_list ,
check_none_or ,
check_short_string ,
check_string ,
check_string_fixed_length ,
check_string_in ,
check_string_or_int ,
check_string_or_int_list ,
2020-06-20 10:37:43 +02:00
check_union ,
2020-06-11 00:54:34 +02:00
check_url ,
equals ,
to_non_negative_int ,
2021-12-15 02:02:38 +01:00
to_wild_value ,
2014-02-14 19:56:55 +01:00
)
2022-03-31 17:23:44 +02:00
from zerver . middleware import LogRequests , parse_client
2023-02-14 22:45:00 +01:00
from zerver . models import Client , Realm , UserProfile , clear_client_cache , get_realm , get_user
2020-08-27 22:46:39 +02:00
if settings . ZILENCER_ENABLED :
from zilencer . models import RemoteZulipServer
2014-02-07 22:47:30 +01:00
2022-06-08 04:52:09 +02:00
if TYPE_CHECKING :
from django . test . client import _MonkeyPatchedWSGIResponse as TestHttpResponse
2014-02-07 22:47:30 +01:00
2020-07-01 04:19:54 +02:00
class DecoratorTestCase ( ZulipTestCase ) :
2021-04-30 02:32:58 +02:00
def test_parse_client ( self ) - > None :
2020-03-08 21:12:38 +01:00
req = HostRequestMock ( )
2021-04-30 02:32:58 +02:00
self . assertEqual ( parse_client ( req ) , ( " Unspecified " , None ) )
2020-03-08 21:12:38 +01:00
2022-05-12 08:28:00 +02:00
req = HostRequestMock ( )
2021-02-12 08:19:30 +01:00
req . META [
2021-02-12 08:20:45 +01:00
" 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 "
2021-04-30 02:32:58 +02:00
self . assertEqual ( parse_client ( req ) , ( " ZulipElectron " , " 4.0.3 " ) )
2020-03-08 21:12:38 +01:00
2022-05-12 08:28:00 +02:00
req = HostRequestMock ( )
2021-02-12 08:20:45 +01:00
req . META [ " HTTP_USER_AGENT " ] = " ZulipDesktop/0.4.4 (Mac) "
2021-04-30 02:32:58 +02:00
self . assertEqual ( parse_client ( req ) , ( " ZulipDesktop " , " 0.4.4 " ) )
2020-03-08 21:12:38 +01:00
2022-05-12 08:28:00 +02:00
req = HostRequestMock ( )
2021-02-12 08:20:45 +01:00
req . META [ " HTTP_USER_AGENT " ] = " ZulipMobile/26.22.145 (Android 10) "
2021-04-30 02:32:58 +02:00
self . assertEqual ( parse_client ( req ) , ( " ZulipMobile " , " 26.22.145 " ) )
2020-03-08 21:12:38 +01:00
2022-05-12 08:28:00 +02:00
req = HostRequestMock ( )
2021-02-12 08:20:45 +01:00
req . META [ " HTTP_USER_AGENT " ] = " ZulipMobile/26.22.145 (iOS 13.3.1) "
2021-04-30 02:32:58 +02:00
self . assertEqual ( parse_client ( req ) , ( " ZulipMobile " , " 26.22.145 " ) )
2020-03-08 21:12:38 +01:00
# TODO: This should ideally be Firefox.
2022-05-12 08:28:00 +02:00
req = HostRequestMock ( )
2021-02-12 08:19:30 +01:00
req . META [
2021-02-12 08:20:45 +01:00
" HTTP_USER_AGENT "
] = " Mozilla/5.0 (X11; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0 "
2021-04-30 02:32:58 +02:00
self . assertEqual ( parse_client ( req ) , ( " Mozilla " , None ) )
2020-03-08 21:12:38 +01:00
# TODO: This should ideally be Chrome.
2022-05-12 08:28:00 +02:00
req = HostRequestMock ( )
2021-02-12 08:19:30 +01:00
req . META [
2021-02-12 08:20:45 +01:00
" 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 "
2021-04-30 02:32:58 +02:00
self . assertEqual ( parse_client ( req ) , ( " Mozilla " , None ) )
2020-03-08 21:12:38 +01:00
# TODO: This should ideally be Mobile Safari if we had better user-agent parsing.
2022-05-12 08:28:00 +02:00
req = HostRequestMock ( )
2021-02-12 08:19:30 +01:00
req . META [
2021-02-12 08:20:45 +01:00
" 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 "
2021-04-30 02:32:58 +02:00
self . assertEqual ( parse_client ( req ) , ( " Mozilla " , None ) )
2016-07-09 11:10:10 +02:00
2022-03-31 17:23:44 +02:00
post_req_with_client = HostRequestMock ( )
post_req_with_client . POST [ " client " ] = " test_client_1 "
post_req_with_client . META [ " HTTP_USER_AGENT " ] = " ZulipMobile/26.22.145 (iOS 13.3.1) "
self . assertEqual ( parse_client ( post_req_with_client ) , ( " test_client_1 " , None ) )
get_req_with_client = HostRequestMock ( )
get_req_with_client . GET [ " client " ] = " test_client_2 "
get_req_with_client . META [ " HTTP_USER_AGENT " ] = " ZulipMobile/26.22.145 (iOS 13.3.1) "
self . assertEqual ( parse_client ( get_req_with_client ) , ( " test_client_2 " , None ) )
def test_unparsable_user_agent ( self ) - > None :
request = HttpRequest ( )
request . POST [ " param " ] = " test "
request . META [ " HTTP_USER_AGENT " ] = " mocked should fail "
with mock . patch (
" zerver.middleware.parse_client " , side_effect = JsonableError ( " message " )
) as m , self . assertLogs ( level = " ERROR " ) :
2022-07-22 03:06:45 +02:00
LogRequests ( lambda request : HttpResponse ( ) ) . process_request ( request )
2022-03-31 17:23:44 +02:00
request_notes = RequestNotes . get_notes ( request )
self . assertEqual ( request_notes . client_name , " Unparsable " )
m . assert_called_once ( )
2018-11-09 19:45:25 +01:00
def test_REQ_aliases ( self ) - > None :
@has_request_variables
2021-02-12 08:19:30 +01:00
def double (
2022-01-11 09:44:20 +01:00
request : HttpRequest ,
x : int = REQ ( whence = " number " , aliases = [ " x " , " n " ] , json_validator = check_int ) ,
2021-07-26 17:17:16 +02:00
) - > HttpResponse :
return json_response ( data = { " number " : x + x } )
2018-11-09 19:45:25 +01:00
2021-07-26 17:20:16 +02:00
request = HostRequestMock ( post_data = { " bogus " : " 5555 " } )
2018-11-09 19:45:25 +01:00
with self . assertRaises ( RequestVariableMissingError ) :
double ( request )
2021-07-26 17:20:16 +02:00
request = HostRequestMock ( post_data = { " number " : " 3 " } )
2021-07-26 17:17:16 +02:00
self . assertEqual ( orjson . loads ( double ( request ) . content ) . get ( " number " ) , 6 )
2018-11-09 19:45:25 +01:00
2021-07-26 17:20:16 +02:00
request = HostRequestMock ( post_data = { " x " : " 4 " } )
2021-07-26 17:17:16 +02:00
self . assertEqual ( orjson . loads ( double ( request ) . content ) . get ( " number " ) , 8 )
2018-11-09 19:45:25 +01:00
2021-07-26 17:20:16 +02:00
request = HostRequestMock ( post_data = { " n " : " 5 " } )
2021-07-26 17:17:16 +02:00
self . assertEqual ( orjson . loads ( double ( request ) . content ) . get ( " number " ) , 10 )
2018-11-09 19:45:25 +01:00
2021-07-26 17:20:16 +02:00
request = HostRequestMock ( post_data = { " number " : " 6 " , " x " : " 7 " } )
2023-01-02 20:50:23 +01:00
with self . assertRaises ( RequestConfusingParamsError ) as cm :
2018-11-09 19:45:25 +01:00
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 :
2022-01-11 10:10:56 +01:00
def my_converter ( var_name : str , data : str ) - > List [ int ] :
2020-08-07 01:09:47 +02:00
lst = orjson . loads ( data )
2014-02-14 19:56:55 +01:00
if not isinstance ( lst , list ) :
2021-02-12 08:20:45 +01:00
raise ValueError ( " not a list " )
2014-02-11 16:37:56 +01:00
if 13 in lst :
2021-02-12 08:20:45 +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
2021-02-12 08:19:30 +01:00
def get_total (
2021-04-30 00:15:33 +02:00
request : HttpRequest , numbers : Sequence [ int ] = REQ ( converter = my_converter )
2021-07-26 17:17:16 +02:00
) - > HttpResponse :
return json_response ( data = { " number " : sum ( numbers ) } )
2014-02-07 22:47:30 +01:00
2021-07-09 17:40:21 +02:00
request = HostRequestMock ( )
2014-02-07 22:47:30 +01:00
with self . assertRaises ( RequestVariableMissingError ) :
get_total ( request )
2021-02-12 08:20:45 +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 " )
2021-02-12 08:20:45 +01:00
request . POST [ " numbers " ] = orjson . dumps ( " { fun: unfun} " ) . decode ( )
2021-07-26 19:33:26 +02:00
with self . assertRaises ( JsonableError ) as jsonable_error_cm :
2017-03-05 08:34:28 +01:00
get_total ( request )
2021-07-26 19:33:26 +02:00
self . assertEqual (
str ( jsonable_error_cm . exception ) , " Bad value for ' numbers ' : \" { fun: unfun} \" "
)
2017-03-05 08:34:28 +01:00
2021-02-12 08:20:45 +01:00
request . POST [ " numbers " ] = orjson . dumps ( [ 2 , 3 , 5 , 8 , 13 , 21 ] ) . decode ( )
2021-07-26 19:33:26 +02:00
with self . assertRaises ( JsonableError ) as jsonable_error_cm :
2014-02-11 16:37:56 +01:00
get_total ( request )
2021-07-26 19:33:26 +02:00
self . assertEqual ( str ( jsonable_error_cm . exception ) , " 13 is an unlucky number! " )
2014-02-11 16:37:56 +01:00
2021-02-12 08:20:45 +01:00
request . POST [ " numbers " ] = orjson . dumps ( [ 1 , 2 , 3 , 4 , 5 , 6 ] ) . decode ( )
2014-02-07 22:47:30 +01:00
result = get_total ( request )
2021-07-26 17:17:16 +02:00
self . assertEqual ( orjson . loads ( result . content ) . get ( " number " ) , 21 )
2014-02-07 22:47:30 +01:00
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
2021-02-12 08:19:30 +01:00
def get_total (
2021-04-30 00:15:33 +02:00
request : HttpRequest , numbers : Sequence [ int ] = REQ ( json_validator = check_list ( check_int ) )
2021-07-26 17:17:16 +02:00
) - > HttpResponse :
return json_response ( data = { " number " : sum ( numbers ) } )
2014-02-07 22:47:30 +01:00
2021-07-09 17:40:21 +02:00
request = HostRequestMock ( )
2014-02-07 22:47:30 +01:00
with self . assertRaises ( RequestVariableMissingError ) :
get_total ( request )
2021-02-12 08:20:45 +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
2021-02-12 08:20:45 +01:00
request . POST [ " numbers " ] = orjson . dumps ( [ 1 , 2 , " what? " , 4 , 5 , 6 ] ) . decode ( )
2014-02-07 22:47:30 +01:00
with self . assertRaises ( JsonableError ) as cm :
get_total ( request )
2021-02-12 08:20:45 +01:00
self . assertEqual ( str ( cm . exception ) , " numbers[2] is not an integer " )
2014-02-07 22:47:30 +01:00
2021-02-12 08:20:45 +01:00
request . POST [ " numbers " ] = orjson . dumps ( [ 1 , 2 , 3 , 4 , 5 , 6 ] ) . decode ( )
2014-02-07 22:47:30 +01:00
result = get_total ( request )
2021-07-26 17:17:16 +02:00
self . assertEqual ( orjson . loads ( result . content ) . get ( " number " ) , 21 )
2014-02-07 22:47:30 +01:00
2018-05-04 00:22:00 +02:00
def test_REQ_str_validator ( self ) - > None :
@has_request_variables
2021-02-12 08:19:30 +01:00
def get_middle_characters (
request : HttpRequest , value : str = REQ ( str_validator = check_string_fixed_length ( 5 ) )
2021-07-26 17:17:16 +02:00
) - > HttpResponse :
return json_response ( data = { " value " : value [ 1 : - 1 ] } )
2018-05-04 00:22:00 +02:00
2021-07-09 17:40:21 +02:00
request = HostRequestMock ( )
2018-05-04 00:22:00 +02:00
with self . assertRaises ( RequestVariableMissingError ) :
get_middle_characters ( request )
2021-02-12 08:20:45 +01:00
request . POST [ " value " ] = " long_value "
2018-05-04 00:22:00 +02:00
with self . assertRaises ( JsonableError ) as cm :
get_middle_characters ( request )
2021-02-12 08:20:45 +01:00
self . assertEqual ( str ( cm . exception ) , " value has incorrect length 10; should be 5 " )
2018-05-04 00:22:00 +02:00
2021-02-12 08:20:45 +01:00
request . POST [ " value " ] = " valid "
2018-05-04 00:22:00 +02:00
result = get_middle_characters ( request )
2021-07-26 17:17:16 +02:00
self . assertEqual ( orjson . loads ( result . content ) . get ( " value " ) , " ali " )
2018-05-04 00:22:00 +02:00
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
2021-02-12 08:19:30 +01:00
def get_payload (
2021-02-12 08:20:45 +01:00
request : HttpRequest , payload : Dict [ str , Any ] = REQ ( argument_type = " body " )
2021-07-26 17:17:16 +02:00
) - > HttpResponse :
return json_response ( data = { " payload " : payload } )
2016-05-06 21:19:34 +02:00
2021-12-15 02:30:39 +01:00
request = HostRequestMock ( )
request . body = b " \xde \xad \xbe \xef "
with self . assertRaises ( JsonableError ) as cm :
get_payload ( request )
self . assertEqual ( str ( cm . exception ) , " Malformed payload " )
2020-02-14 09:32:06 +01:00
request = HostRequestMock ( )
2021-07-09 10:10:58 +02:00
request . body = b " notjson "
2016-05-06 21:19:34 +02:00
with self . assertRaises ( JsonableError ) as cm :
get_payload ( request )
2021-02-12 08:20:45 +01:00
self . assertEqual ( str ( cm . exception ) , " Malformed JSON " )
2016-05-06 21:19:34 +02:00
2021-07-09 10:10:58 +02:00
request . body = b ' { " a " : " b " } '
2021-07-26 17:17:16 +02:00
self . assertEqual ( orjson . loads ( get_payload ( request ) . content ) . get ( " payload " ) , { " a " : " b " } )
2016-05-06 21:19:34 +02:00
2020-12-15 20:46:25 +01:00
def logger_output ( self , output_string : str , type : str , logger : str ) - > str :
2021-02-12 08:20:45 +01:00
return f " { type . upper ( ) } :zulip.zerver. { logger } : { output_string } "
2020-12-15 20:46:25 +01:00
2020-08-20 00:32:15 +02:00
def test_webhook_view ( self ) - > None :
2021-02-12 08:20:45 +01:00
@webhook_view ( " ClientName " )
2021-07-26 16:29:19 +02:00
def my_webhook ( request : HttpRequest , user_profile : UserProfile ) - > HttpResponse :
return json_response ( msg = user_profile . email )
2016-05-18 20:35:35 +02:00
2021-02-12 08:20:45 +01:00
@webhook_view ( " ClientName " )
2021-07-26 16:29:19 +02:00
def my_webhook_raises_exception (
request : HttpRequest , user_profile : UserProfile
) - > HttpResponse :
2017-07-19 05:08:51 +02:00
raise Exception ( " raised by webhook function " )
2021-02-12 08:20:45 +01:00
@webhook_view ( " ClientName " )
2020-08-19 22:26:38 +02:00
def my_webhook_raises_exception_unsupported_event (
2021-02-12 08:19:30 +01:00
request : HttpRequest , user_profile : UserProfile
2021-07-26 16:29:19 +02:00
) - > HttpResponse :
2022-11-17 09:30:48 +01:00
raise UnsupportedWebhookEventTypeError ( " test_event " )
2019-06-06 05:55:09 +02:00
2021-02-12 08:20:45 +01:00
webhook_bot_email = " webhook-bot@zulip.com "
webhook_bot_realm = get_realm ( " zulip " )
2017-05-23 20:57:59 +02:00
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 )
2016-07-09 10:12:46 +02:00
2017-08-29 07:40:56 +02:00
request = HostRequestMock ( )
2021-02-12 08:20:45 +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 " ) :
2020-06-23 04:30:55 +02:00
my_webhook ( request )
2016-07-09 10:12:46 +02:00
# Start a valid request here
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( )
2021-02-12 08:20:45 +01:00
request . POST [ " api_key " ] = webhook_bot_api_key
2020-07-15 12:46:41 +02:00
with self . assertLogs ( level = " WARNING " ) as m :
2021-02-12 08:19:30 +01:00
with self . assertRaisesRegex (
JsonableError , " Account is not associated with this subdomain "
) :
2020-06-23 04:30:55 +02:00
api_result = my_webhook ( request )
2020-07-15 12:46:41 +02:00
self . assertEqual (
m . output ,
2021-02-12 08:19:30 +01:00
[
" WARNING:root:User {} ( {} ) attempted to access API on wrong subdomain ( {} ) " . format (
webhook_bot_email , " zulip " , " "
)
] ,
2020-07-15 12:46:41 +02:00
)
2016-10-12 10:43:20 +02:00
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( )
request . POST [ " api_key " ] = webhook_bot_api_key
2020-07-15 12:46:41 +02:00
with self . assertLogs ( level = " WARNING " ) as m :
2021-02-12 08:19:30 +01:00
with self . assertRaisesRegex (
JsonableError , " Account is not associated with this subdomain "
) :
2017-10-02 22:37:20 +02:00
request . host = " acme. " + settings . EXTERNAL_HOST
2020-06-23 04:30:55 +02:00
api_result = my_webhook ( request )
2020-07-15 12:46:41 +02:00
self . assertEqual (
m . output ,
2021-02-12 08:19:30 +01:00
[
" WARNING:root:User {} ( {} ) attempted to access API on wrong subdomain ( {} ) " . format (
webhook_bot_email , " zulip " , " acme "
)
] ,
2020-07-15 12:46:41 +02:00
)
2016-10-12 10:43:20 +02:00
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
2020-12-15 20:46:25 +01:00
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( )
request . host = " zulip.testserver "
request . POST [ " api_key " ] = webhook_bot_api_key
2021-02-12 08:20:45 +01:00
with self . assertLogs ( " zulip.zerver.webhooks " , level = " INFO " ) as log :
2017-07-19 05:08:51 +02:00
with self . assertRaisesRegex ( Exception , " raised by webhook function " ) :
2021-07-09 10:10:58 +02:00
request . body = b " {} "
2021-02-12 08:20:45 +01:00
request . content_type = " application/json "
2020-06-23 04:30:55 +02:00
my_webhook_raises_exception ( request )
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
2020-12-15 20:46:25 +01:00
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( )
request . host = " zulip.testserver "
request . POST [ " api_key " ] = webhook_bot_api_key
2021-02-12 08:20:45 +01:00
with self . assertLogs ( " zulip.zerver.webhooks " , level = " INFO " ) as log :
2017-07-19 05:08:51 +02:00
with self . assertRaisesRegex ( Exception , " raised by webhook function " ) :
2021-07-09 10:10:58 +02:00
request . body = b " notjson "
2021-02-12 08:20:45 +01:00
request . content_type = " text/plain "
2020-06-23 04:30:55 +02:00
my_webhook_raises_exception ( request )
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
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( )
request . host = " zulip.testserver "
request . POST [ " api_key " ] = webhook_bot_api_key
2021-02-12 08:20:45 +01:00
with self . assertLogs ( " zulip.zerver.webhooks " , level = " ERROR " ) as log :
2017-07-20 03:43:04 +02:00
with self . assertRaisesRegex ( Exception , " raised by webhook function " ) :
2021-07-09 10:10:58 +02:00
request . body = b " invalidjson "
2021-02-12 08:20:45 +01:00
request . content_type = " application/json "
request . META [ " HTTP_X_CUSTOM_HEADER " ] = " custom_value "
2020-06-23 04:30:55 +02:00
my_webhook_raises_exception ( request )
2017-07-20 03:43:04 +02:00
2021-02-12 08:19:30 +01:00
self . assertIn (
2021-02-12 08:20:45 +01:00
self . logger_output ( " raised by webhook function \n " , " error " , " webhooks " ) , log . output [ 0 ]
2021-02-12 08:19:30 +01:00
)
2019-06-06 05:55:09 +02:00
2020-08-19 22:26:38 +02:00
# Test when an unsupported webhook event occurs
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( )
request . host = " zulip.testserver "
request . POST [ " api_key " ] = webhook_bot_api_key
2020-12-15 20:46:25 +01:00
exception_msg = " The ' test_event ' event isn ' t currently supported by the ClientName webhook "
2021-02-12 08:20:45 +01:00
with self . assertLogs ( " zulip.zerver.webhooks.unsupported " , level = " ERROR " ) as log :
2022-11-17 09:30:48 +01:00
with self . assertRaisesRegex ( UnsupportedWebhookEventTypeError , exception_msg ) :
2021-07-09 10:10:58 +02:00
request . body = b " invalidjson "
2021-02-12 08:20:45 +01:00
request . content_type = " application/json "
request . META [ " HTTP_X_CUSTOM_HEADER " ] = " custom_value "
2020-08-19 22:26:38 +02:00
my_webhook_raises_exception_unsupported_event ( request )
2019-06-06 05:55:09 +02:00
2021-02-12 08:19:30 +01:00
self . assertIn (
2021-02-12 08:20:45 +01:00
self . logger_output ( exception_msg , " error " , " webhooks.unsupported " ) , log . output [ 0 ]
2021-02-12 08:19:30 +01:00
)
2017-07-19 05:08:51 +02:00
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( )
request . host = " zulip.testserver "
request . POST [ " api_key " ] = webhook_bot_api_key
2016-07-09 10:12:46 +02:00
with self . settings ( RATE_LIMITING = True ) :
2021-02-12 08:20:45 +01:00
with mock . patch ( " zerver.decorator.rate_limit_user " ) as rate_limit_mock :
2021-07-26 16:29:19 +02:00
api_result = orjson . loads ( my_webhook ( request ) . content ) . get ( " msg " )
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
2021-02-14 00:03:40 +01:00
change_user_is_active ( webhook_bot , False )
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( )
request . host = " zulip.testserver "
request . POST [ " api_key " ] = webhook_bot_api_key
2018-08-10 00:58:31 +02:00
with self . assertRaisesRegex ( JsonableError , " Account is deactivated " ) :
2020-06-23 04:30:55 +02:00
my_webhook ( request )
2016-07-09 10:12:46 +02:00
# Reactive the user, but deactivate their realm.
2021-02-14 00:03:40 +01:00
change_user_is_active ( webhook_bot , True )
2016-07-09 10:12:46 +02:00
webhook_bot . realm . deactivated = True
webhook_bot . realm . save ( )
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( )
request . host = " zulip.testserver "
request . POST [ " api_key " ] = webhook_bot_api_key
2018-03-17 00:51:11 +01:00
with self . assertRaisesRegex ( JsonableError , " This organization has been deactivated " ) :
2020-06-23 04:30:55 +02:00
my_webhook ( request )
2016-05-18 20:35:35 +02:00
2021-02-12 08:19:30 +01: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 )
2021-07-26 16:29:19 +02:00
def my_rate_limited_view ( request : HttpRequest , user_profile : UserProfile ) - > HttpResponse :
2022-01-31 13:44:02 +01:00
return json_success ( request ) # nocoverage # mock prevents this from being called
2018-12-11 20:46:52 +01:00
@authenticated_rest_api_view ( skip_rate_limiting = True )
2021-07-26 16:29:19 +02:00
def my_unlimited_view ( request : HttpRequest , user_profile : UserProfile ) - > HttpResponse :
2022-01-31 13:44:02 +01:00
return json_success ( request )
2018-12-11 20:46:52 +01:00
request = HostRequestMock ( host = " zulip.testserver " )
2021-02-12 08:20:45 +01:00
request . META [ " HTTP_AUTHORIZATION " ] = self . encode_email ( self . example_email ( " hamlet " ) )
request . method = " POST "
2022-08-14 21:14:52 +02:00
with mock . patch ( " zerver.decorator.rate_limit_user " ) as rate_limit_mock :
2020-06-23 04:30:55 +02:00
result = my_unlimited_view ( request )
2020-09-02 10:53:41 +02:00
self . assert_json_success ( result )
self . assertFalse ( rate_limit_mock . called )
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( host = " zulip.testserver " )
request . META [ " HTTP_AUTHORIZATION " ] = self . encode_email ( self . example_email ( " hamlet " ) )
request . method = " POST "
2022-08-14 21:14:52 +02:00
with mock . patch ( " zerver.decorator.rate_limit_user " ) as rate_limit_mock :
2020-06-23 04:30:55 +02:00
result = my_rate_limited_view ( request )
2020-09-02 10:53:41 +02:00
# Don't assert json_success, since it'll be the rate_limit mock object
self . assertTrue ( rate_limit_mock . called )
2018-12-11 20:46:52 +01:00
def test_authenticated_uploads_api_view ( self ) - > None :
@authenticated_uploads_api_view ( skip_rate_limiting = False )
2021-07-26 16:29:19 +02:00
def my_rate_limited_view ( request : HttpRequest , user_profile : UserProfile ) - > HttpResponse :
2022-01-31 13:44:02 +01:00
return json_success ( request ) # nocoverage # mock prevents this from being called
2018-12-11 20:46:52 +01:00
@authenticated_uploads_api_view ( skip_rate_limiting = True )
2021-07-26 16:29:19 +02:00
def my_unlimited_view ( request : HttpRequest , user_profile : UserProfile ) - > HttpResponse :
2022-01-31 13:44:02 +01:00
return json_success ( request )
2018-12-11 20:46:52 +01:00
request = HostRequestMock ( host = " zulip.testserver " )
2021-02-12 08:20:45 +01:00
request . method = " POST "
request . POST [ " api_key " ] = get_api_key ( self . example_user ( " hamlet " ) )
2022-08-14 21:14:52 +02:00
with mock . patch ( " zerver.decorator.rate_limit_user " ) as rate_limit_mock :
2020-06-23 04:30:55 +02:00
result = my_unlimited_view ( request )
2020-09-02 10:53:41 +02:00
self . assert_json_success ( result )
self . assertFalse ( rate_limit_mock . called )
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( host = " zulip.testserver " )
request . method = " POST "
request . POST [ " api_key " ] = get_api_key ( self . example_user ( " hamlet " ) )
2022-08-14 21:14:52 +02:00
with mock . patch ( " zerver.decorator.rate_limit_user " ) as rate_limit_mock :
2020-06-23 04:30:55 +02:00
result = my_rate_limited_view ( request )
2020-09-02 10:53:41 +02:00
# Don't assert json_success, since it'll be the rate_limit mock object
self . assertTrue ( rate_limit_mock . called )
2018-12-11 20:46:52 +01:00
def test_authenticated_json_view ( self ) - > None :
2021-07-26 16:29:19 +02:00
def my_view ( request : HttpRequest , user_profile : UserProfile ) - > HttpResponse :
2022-01-31 13:44:02 +01:00
return json_success ( request )
2018-12-11 20:46:52 +01:00
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 " )
2021-02-12 08:20:45 +01:00
request . method = " POST "
2018-12-11 20:46:52 +01:00
request . user = self . example_user ( " hamlet " )
2022-08-14 21:14:52 +02:00
with mock . patch ( " zerver.decorator.rate_limit_user " ) as rate_limit_mock :
2020-06-23 04:30:55 +02:00
result = my_unlimited_view ( request )
2020-09-02 10:53:41 +02:00
self . assert_json_success ( result )
self . assertFalse ( rate_limit_mock . called )
2022-05-12 08:28:00 +02:00
request = HostRequestMock ( host = " zulip.testserver " )
request . method = " POST "
request . user = self . example_user ( " hamlet " )
2022-08-14 21:14:52 +02:00
with mock . patch ( " zerver.decorator.rate_limit_user " ) as rate_limit_mock :
2020-06-23 04:30:55 +02:00
result = my_rate_limited_view ( request )
2020-09-02 10:53:41 +02:00
# 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
2021-02-12 08:19:30 +01:00
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 " )
2021-07-26 16:29:19 +02:00
def my_webhook_raises_exception (
request : HttpRequest , user_profile : UserProfile
) - > HttpResponse :
2018-03-27 04:34:43 +02:00
raise Exception ( " raised by webhook function " )
2021-02-12 08:20:45 +01:00
webhook_bot_email = " webhook-bot@zulip.com "
2018-03-27 04:34:43 +02:00
request = HostRequestMock ( )
2021-02-12 08:20:45 +01:00
request . META [ " HTTP_AUTHORIZATION " ] = self . encode_email ( webhook_bot_email )
request . method = " POST "
2018-03-27 04:34:43 +02:00
request . host = " zulip.testserver "
2021-07-09 10:10:58 +02:00
request . body = b " {} "
2021-02-12 08:20:45 +01:00
request . content_type = " text/plain "
2018-03-27 04:34:43 +02:00
2021-07-26 19:23:26 +02:00
with self . assertLogs ( " zulip.zerver.webhooks " ) as logger :
2018-03-27 04:34:43 +02:00
with self . assertRaisesRegex ( Exception , " raised by webhook function " ) :
2020-06-23 04:30:55 +02:00
my_webhook_raises_exception ( request )
2018-03-27 04:34:43 +02:00
2021-07-26 19:23:26 +02:00
self . assertIn ( " raised by webhook function " , logger . output [ 0 ] )
2019-06-06 05:55:09 +02:00
2020-08-19 22:26:38 +02:00
def test_authenticated_rest_api_view_logging_unsupported_event ( self ) - > None :
2019-06-06 05:55:09 +02:00
@authenticated_rest_api_view ( webhook_client_name = " ClientName " )
2021-07-26 16:29:19 +02:00
def my_webhook_raises_exception (
request : HttpRequest , user_profile : UserProfile
) - > HttpResponse :
2022-11-17 09:30:48 +01:00
raise UnsupportedWebhookEventTypeError ( " test_event " )
2019-06-06 05:55:09 +02:00
2021-02-12 08:20:45 +01:00
webhook_bot_email = " webhook-bot@zulip.com "
2019-06-06 05:55:09 +02:00
request = HostRequestMock ( )
2021-02-12 08:20:45 +01:00
request . META [ " HTTP_AUTHORIZATION " ] = self . encode_email ( webhook_bot_email )
request . method = " POST "
2019-06-06 05:55:09 +02:00
request . host = " zulip.testserver "
2021-07-09 10:10:58 +02:00
request . body = b " {} "
2021-02-12 08:20:45 +01:00
request . content_type = " text/plain "
2019-06-06 05:55:09 +02:00
2021-02-12 08:19:30 +01:00
with mock . patch (
2021-02-12 08:20:45 +01:00
" zerver.decorator.webhook_unsupported_events_logger.exception "
2021-02-12 08:19:30 +01:00
) as mock_exception :
exception_msg = (
" The ' test_event ' event isn ' t currently supported by the ClientName webhook "
)
2022-11-17 09:30:48 +01:00
with self . assertRaisesRegex ( UnsupportedWebhookEventTypeError , exception_msg ) :
2020-06-23 04:30:55 +02:00
my_webhook_raises_exception ( request )
2019-06-06 05:55:09 +02:00
2020-09-04 03:38:24 +02:00
mock_exception . assert_called_with ( exception_msg , stack_info = True )
2018-03-27 04:34:43 +02:00
def test_authenticated_rest_api_view_with_non_webhook_view ( self ) - > None :
@authenticated_rest_api_view ( )
2021-02-12 08:19:30 +01:00
def non_webhook_view_raises_exception (
request : HttpRequest , user_profile : UserProfile
2021-07-26 16:29:19 +02:00
) - > HttpResponse :
2018-03-27 04:34:43 +02:00
raise Exception ( " raised by a non-webhook view " )
request = HostRequestMock ( )
2021-02-12 08:20:45 +01:00
request . META [ " HTTP_AUTHORIZATION " ] = self . encode_email ( " aaron@zulip.com " )
request . method = " POST "
2018-03-27 04:34:43 +02:00
request . host = " zulip.testserver "
2021-07-09 10:10:58 +02:00
request . body = b " {} "
2021-02-12 08:20:45 +01:00
request . content_type = " application/json "
2018-03-27 04:34:43 +02:00
2021-02-12 08:20:45 +01:00
with mock . patch ( " zerver.decorator.webhook_logger.exception " ) as mock_exception :
2018-03-27 04:34:43 +02:00
with self . assertRaisesRegex ( Exception , " raised by a non-webhook view " ) :
non_webhook_view_raises_exception ( request )
2020-09-02 10:53:41 +02:00
self . assertFalse ( mock_exception . called )
2018-03-27 04:34:43 +02:00
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 )
2020-06-10 06:41:04 +02:00
credentials = f " { user_profile . email } : { api_key } "
2021-08-02 23:20:39 +02:00
api_auth = " Digest " + base64 . b64encode ( credentials . encode ( ) ) . decode ( )
2021-02-12 08:20:45 +01:00
result = self . client_post ( " /api/v1/external/zendesk " , { } , HTTP_AUTHORIZATION = api_auth )
2018-04-26 07:14:21 +02:00
self . assert_json_error ( result , " This endpoint requires HTTP basic authentication. " )
2021-08-02 23:20:39 +02:00
api_auth = " Basic " + base64 . b64encode ( b " foo " ) . decode ( )
2021-02-12 08:20:45 +01:00
result = self . client_post ( " /api/v1/external/zendesk " , { } , HTTP_AUTHORIZATION = api_auth )
2021-02-12 08:19:30 +01:00
self . assert_json_error (
result , " Invalid authorization header for basic auth " , status_code = 401
)
2018-04-26 07:14:21 +02:00
2021-02-12 08:20:45 +01:00
result = self . client_post ( " /api/v1/external/zendesk " , { } )
2021-02-12 08:19:30 +01:00
self . assert_json_error (
result , " Missing authorization header for basic auth " , status_code = 401
)
2018-04-26 07:14:21 +02:00
2020-07-01 04:19:54 +02:00
class RateLimitTestCase ( ZulipTestCase ) :
2022-08-15 00:17:12 +02:00
@staticmethod
@public_json_view
2022-08-14 21:14:52 +02:00
def ratelimited_json_view (
2022-08-15 00:17:12 +02:00
req : HttpRequest , maybe_user_profile : Union [ AnonymousUser , UserProfile ] , /
) - > HttpResponse :
return HttpResponse ( " some value " )
2021-07-26 17:17:16 +02:00
2022-08-14 21:14:52 +02:00
@staticmethod
@web_public_view
def ratelimited_web_view ( req : HttpRequest ) - > HttpResponse :
return HttpResponse ( " some value " )
2022-08-15 00:17:12 +02:00
def check_rate_limit_public_or_user_views (
2022-08-14 21:14:52 +02:00
self ,
remote_addr : str ,
client_name : str ,
expect_rate_limit : bool ,
check_web_view : bool = False ,
2022-08-15 00:17:12 +02:00
) - > None :
META = { " REMOTE_ADDR " : remote_addr , " PATH_INFO " : " test " }
2016-07-09 07:47:07 +02:00
2022-08-15 00:17:12 +02:00
request = HostRequestMock ( host = " zulip.testserver " , client_name = client_name , meta_data = META )
2022-08-14 21:14:52 +02:00
view_func = self . ratelimited_web_view if check_web_view else self . ratelimited_json_view
2016-07-09 07:47:07 +02:00
2022-08-15 00:02:28 +02:00
with mock . patch (
2022-08-14 21:14:52 +02:00
" zerver.lib.rate_limiter.RateLimitedUser "
2022-08-15 00:02:28 +02:00
) as rate_limit_user_mock , mock . patch (
2022-08-17 16:22:26 +02:00
" zerver.lib.rate_limiter.RateLimitedIPAddr "
2022-08-15 04:29:21 +02:00
) as rate_limit_ip_mock :
2022-08-14 21:14:52 +02:00
self . assert_in_success_response ( [ " some value " ] , view_func ( request ) )
2022-08-15 00:17:12 +02:00
self . assertEqual ( rate_limit_ip_mock . called , expect_rate_limit )
self . assertFalse ( rate_limit_user_mock . called )
2016-07-09 07:47:07 +02:00
2022-08-15 00:17:12 +02:00
# We need to recreate the request, because process_client mutates client on
# the associated RequestNotes, causing the request to be incorrectly rate limited, since
# should_rate_limit checks the client to determine if rate limiting should be skipped.
user = self . example_user ( " hamlet " )
request = HostRequestMock (
user_profile = user , host = " zulip.testserver " , client_name = client_name , meta_data = META
)
with mock . patch (
2022-08-14 21:14:52 +02:00
" zerver.lib.rate_limiter.RateLimitedUser "
2022-08-15 00:17:12 +02:00
) as rate_limit_user_mock , mock . patch (
2022-08-17 16:22:26 +02:00
" zerver.lib.rate_limiter.RateLimitedIPAddr "
2022-08-15 04:29:21 +02:00
) as rate_limit_ip_mock :
2022-08-14 21:14:52 +02:00
self . assert_in_success_response ( [ " some value " ] , view_func ( request ) )
2022-08-15 00:17:12 +02:00
self . assertEqual ( rate_limit_user_mock . called , expect_rate_limit )
2021-07-08 14:46:47 +02:00
self . assertFalse ( rate_limit_ip_mock . called )
2016-07-09 07:47:07 +02:00
2022-08-15 00:02:28 +02:00
def test_internal_local_clients_skip_rate_limiting ( self ) - > None :
2016-07-09 07:47:07 +02:00
with self . settings ( RATE_LIMITING = True ) :
2022-08-15 00:02:28 +02:00
self . check_rate_limit_public_or_user_views (
2022-08-15 00:17:12 +02:00
remote_addr = " 127.0.0.1 " , client_name = " internal " , expect_rate_limit = False
2022-08-15 00:02:28 +02:00
)
2016-07-09 07:47:07 +02:00
2022-08-15 00:02:28 +02:00
def test_debug_clients_skip_rate_limiting ( self ) - > None :
with self . settings ( DEBUG_RATE_LIMITING = True , RATE_LIMITING = True ) :
# Rate limiting is skipped for internal clients with an external address
# when DEBUG_RATE_LIMITING is True.
self . check_rate_limit_public_or_user_views (
2022-08-15 00:17:12 +02:00
remote_addr = " 3.3.3.3 " , client_name = " internal " , expect_rate_limit = False
2022-08-15 00:02:28 +02:00
)
2016-07-09 07:47:07 +02:00
2017-11-05 10:51:25 +01:00
def test_rate_limit_setting_of_false_bypasses_rate_limiting ( self ) - > None :
2016-07-09 07:47:07 +02:00
with self . settings ( RATE_LIMITING = False ) :
2022-08-15 00:02:28 +02:00
self . check_rate_limit_public_or_user_views (
2022-08-15 00:17:12 +02:00
remote_addr = " 3.3.3.3 " , client_name = " external " , expect_rate_limit = False
2022-08-15 00:02:28 +02:00
)
2016-07-09 07:47:07 +02:00
2017-11-05 10:51:25 +01:00
def test_rate_limiting_happens_in_normal_case ( self ) - > None :
2016-07-09 07:47:07 +02:00
with self . settings ( RATE_LIMITING = True ) :
2022-08-15 00:17:12 +02:00
self . check_rate_limit_public_or_user_views (
remote_addr = " 3.3.3.3 " , client_name = " external " , expect_rate_limit = True
)
2022-08-14 21:14:52 +02:00
def test_rate_limiting_web_public_views ( self ) - > None :
with self . settings ( RATE_LIMITING = True ) :
self . check_rate_limit_public_or_user_views (
remote_addr = " 3.3.3.3 " ,
client_name = " external " ,
expect_rate_limit = True ,
check_web_view = True ,
)
2016-07-09 07:47:07 +02:00
2020-08-27 22:46:39 +02:00
@skipUnless ( settings . ZILENCER_ENABLED , " requires zilencer " )
2021-07-08 14:46:47 +02:00
def test_rate_limiting_happens_if_remote_server ( self ) - > None :
2022-08-14 16:16:36 +02:00
user = self . example_user ( " hamlet " )
2021-12-22 14:37:12 +01:00
server_uuid = str ( uuid . uuid4 ( ) )
2021-02-12 08:19:30 +01:00
server = RemoteZulipServer (
uuid = server_uuid ,
api_key = " magic_secret_api_key " ,
hostname = " demo.example.com " ,
last_updated = timezone_now ( ) ,
)
2022-08-14 16:16:36 +02:00
server . save ( )
with self . settings ( RATE_LIMITING = True ) , mock . patch (
2022-08-14 16:19:44 +02:00
" zilencer.auth.rate_limit_remote_server "
2022-08-14 16:16:36 +02:00
) as rate_limit_mock :
result = self . uuid_post (
server_uuid ,
" /api/v1/remotes/push/unregister/all " ,
{ " user_id " : user . id } ,
subdomain = " " ,
)
self . assert_json_success ( result )
2020-08-27 17:20:54 +02:00
2021-07-08 14:46:47 +02:00
self . assertTrue ( rate_limit_mock . called )
2021-02-12 08:19:30 +01:00
2020-07-01 04:19:54 +02:00
class ValidatorTestCase ( ZulipTestCase ) :
2017-11-05 10:51:25 +01:00
def test_check_string ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = " hello "
2021-02-12 08:20:45 +01:00
check_string ( " x " , x )
2014-02-07 22:47:30 +01:00
x = 4
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a string " ) :
check_string ( " x " , x )
2014-02-07 22:47:30 +01:00
2018-05-03 23:22:05 +02:00
def test_check_string_fixed_length ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = " hello "
2021-02-12 08:20:45 +01:00
check_string_fixed_length ( 5 ) ( " x " , x )
2018-05-03 23:22:05 +02:00
x = 4
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a string " ) :
check_string_fixed_length ( 5 ) ( " x " , x )
2018-05-03 23:22:05 +02:00
x = " helloz "
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x has incorrect length 6; should be 5 " ) :
check_string_fixed_length ( 5 ) ( " x " , x )
2018-05-03 23:22:05 +02:00
x = " hi "
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x has incorrect length 2; should be 5 " ) :
check_string_fixed_length ( 5 ) ( " x " , x )
2018-05-03 23:22:05 +02:00
2018-05-03 23:21:16 +02:00
def test_check_capped_string ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = " hello "
2021-02-12 08:20:45 +01:00
check_capped_string ( 5 ) ( " x " , x )
2018-05-03 23:21:16 +02:00
x = 4
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a string " ) :
check_capped_string ( 5 ) ( " x " , x )
2018-05-03 23:21:16 +02:00
x = " helloz "
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is too long \ (limit: 5 characters \ ) " ) :
check_capped_string ( 5 ) ( " x " , x )
2018-05-03 23:21:16 +02:00
x = " hi "
2021-02-12 08:20:45 +01:00
check_capped_string ( 5 ) ( " x " , x )
2018-05-03 23:21:16 +02:00
2020-03-24 20:36:45 +01:00
def test_check_string_in ( self ) - > None :
2021-02-12 08:20:45 +01:00
check_string_in ( [ " valid " , " othervalid " ] ) ( " Test " , " valid " )
with self . assertRaisesRegex ( ValidationError , r " Test is not a string " ) :
check_string_in ( [ " valid " , " othervalid " ] ) ( " Test " , 15 )
check_string_in ( [ " valid " , " othervalid " ] ) ( " Test " , " othervalid " )
with self . assertRaisesRegex ( ValidationError , r " Invalid Test " ) :
check_string_in ( [ " valid " , " othervalid " ] ) ( " Test " , " invalid " )
2020-03-24 20:36:45 +01:00
2019-11-16 17:38:27 +01:00
def test_check_int_in ( self ) - > None :
2020-06-21 02:36:20 +02:00
check_int_in ( [ 1 ] ) ( " Test " , 1 )
with self . assertRaisesRegex ( ValidationError , r " Invalid Test " ) :
check_int_in ( [ 1 ] ) ( " Test " , 2 )
with self . assertRaisesRegex ( ValidationError , r " Test is not an integer " ) :
check_int_in ( [ 1 ] ) ( " Test " , " t " )
2019-11-16 17:38:27 +01:00
2017-11-05 10:51:25 +01:00
def test_check_short_string ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = " hello "
2021-02-12 08:20:45 +01:00
check_short_string ( " x " , x )
2017-11-11 01:37:43 +01:00
2021-02-12 08:20:45 +01:00
x = " x " * 201
2020-06-21 02:36:20 +02:00
with self . assertRaisesRegex ( ValidationError , r " x is too long \ (limit: 50 characters \ ) " ) :
2021-02-12 08:20:45 +01:00
check_short_string ( " x " , x )
2017-11-11 01:37:43 +01:00
x = 4
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a string " ) :
check_short_string ( " x " , x )
2017-11-11 01:37:43 +01:00
2017-11-05 10:51:25 +01:00
def test_check_bool ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = True
2021-02-12 08:20:45 +01:00
check_bool ( " x " , x )
2014-02-07 22:47:30 +01:00
x = 4
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a boolean " ) :
check_bool ( " x " , x )
2014-02-07 22:47:30 +01:00
2017-11-05 10:51:25 +01:00
def test_check_int ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = 5
2021-02-12 08:20:45 +01:00
check_int ( " x " , x )
2014-02-07 22:47:30 +01:00
x = [ { } ]
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not an integer " ) :
check_int ( " x " , x )
2014-02-07 22:47:30 +01:00
2019-02-09 23:57:54 +01:00
def test_to_non_negative_int ( self ) - > None :
2022-01-11 10:10:56 +01:00
self . assertEqual ( to_non_negative_int ( " x " , " 5 " ) , 5 )
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValueError , " argument is negative " ) :
2022-01-11 10:10:56 +01:00
to_non_negative_int ( " x " , " -1 " )
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValueError , re . escape ( " 5 is too large (max 4) " ) ) :
2022-01-11 10:10:56 +01:00
to_non_negative_int ( " x " , " 5 " , max_int_size = 4 )
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValueError , re . escape ( f " { 2 * * 32 } is too large (max { 2 * * 32 - 1 } ) " ) ) :
2022-01-11 10:10:56 +01:00
to_non_negative_int ( " x " , str ( 2 * * 32 ) )
2019-02-09 23:57:54 +01:00
2017-11-05 10:51:25 +01:00
def test_check_float ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = 5.5
2021-02-12 08:20:45 +01:00
check_float ( " x " , x )
2017-03-24 05:23:41 +01:00
x = 5
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a float " ) :
check_float ( " x " , x )
2017-03-24 05:23:41 +01:00
x = [ { } ]
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a float " ) :
check_float ( " x " , x )
2017-03-24 05:23:41 +01:00
2019-01-14 07:28:04 +01:00
def test_check_color ( self ) - > None :
2021-02-12 08:20:45 +01:00
x = [ " #000099 " , " #80ffaa " , " #80FFAA " , " #abcd12 " , " #ffff00 " , " #ff0 " , " #f00 " ] # valid
y = [ " 000099 " , " #80f_aa " , " #80fraa " , " #abcd1234 " , " blue " ] # invalid
2019-01-14 07:28:04 +01:00
z = 5 # invalid
for hex_color in x :
2021-02-12 08:20:45 +01:00
check_color ( " color " , hex_color )
2019-01-14 07:28:04 +01:00
for hex_color in y :
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " color is not a valid hex color code " ) :
check_color ( " color " , hex_color )
2019-01-14 07:28:04 +01:00
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " color is not a string " ) :
check_color ( " color " , z )
2019-01-14 07:28:04 +01:00
2017-11-05 10:51:25 +01:00
def test_check_list ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = 999
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a list " ) :
check_list ( check_string ) ( " x " , x )
2014-02-07 22:47:30 +01:00
x = [ " hello " , 5 ]
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x \ [1 \ ] is not a string " ) :
check_list ( check_string ) ( " x " , x )
2014-02-07 22:47:30 +01:00
x = [ [ " yo " ] , [ " hello " , " goodbye " , 5 ] ]
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x \ [1 \ ] \ [2 \ ] is not a string " ) :
check_list ( check_list ( check_string ) ) ( " x " , x )
2014-02-07 22:47:30 +01:00
x = [ " hello " , " goodbye " , " hello again " ]
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x should have exactly 2 items " ) :
check_list ( check_string , length = 2 ) ( " x " , x )
2014-02-07 22:47:30 +01:00
2017-11-05 10:51:25 +01:00
def test_check_dict ( self ) - > None :
2020-06-21 02:36:20 +02:00
keys : List [ Tuple [ str , Validator [ object ] ] ] = [
2021-02-12 08:20:45 +01:00
( " names " , check_list ( check_string ) ) ,
( " city " , check_string ) ,
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
]
2014-02-07 22:47:30 +01:00
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = {
2021-02-12 08:20:45 +01:00
" names " : [ " alice " , " bob " ] ,
" city " : " Boston " ,
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
}
2021-02-12 08:20:45 +01:00
check_dict ( keys ) ( " x " , x )
2014-02-07 22:47:30 +01:00
x = 999
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a dict " ) :
check_dict ( keys ) ( " x " , x )
2014-02-07 22:47:30 +01:00
x = { }
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " names key is missing from x " ) :
check_dict ( keys ) ( " x " , x )
2014-02-07 22:47:30 +01:00
x = {
2021-02-12 08:20:45 +01:00
" names " : [ " alice " , " bob " , { } ] ,
2014-02-07 22:47:30 +01:00
}
2020-06-21 02:36:20 +02:00
with self . assertRaisesRegex ( ValidationError , r ' x \ [ " names " \ ] \ [2 \ ] is not a string ' ) :
2021-02-12 08:20:45 +01:00
check_dict ( keys ) ( " x " , x )
2014-02-07 22:47:30 +01:00
x = {
2021-02-12 08:20:45 +01:00
" names " : [ " alice " , " bob " ] ,
" city " : 5 ,
2014-02-07 22:47:30 +01:00
}
2020-06-21 02:36:20 +02:00
with self . assertRaisesRegex ( ValidationError , r ' x \ [ " city " \ ] is not a string ' ) :
2021-02-12 08:20:45 +01:00
check_dict ( keys ) ( " x " , x )
2014-02-07 22:47:30 +01:00
2018-01-07 21:20:28 +01:00
x = {
2021-02-12 08:20:45 +01:00
" names " : [ " alice " , " bob " ] ,
" city " : " Boston " ,
2018-01-07 21:20:28 +01:00
}
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x contains a value that is not a string " ) :
check_dict ( value_validator = check_string ) ( " x " , x )
2018-01-07 21:20:28 +01:00
x = {
2021-02-12 08:20:45 +01:00
" city " : " Boston " ,
2018-01-07 21:20:28 +01:00
}
2021-02-12 08:20:45 +01:00
check_dict ( value_validator = check_string ) ( " x " , x )
2018-01-07 21:20:28 +01:00
2017-03-26 08:11:45 +02:00
# test dict_only
x = {
2021-02-12 08:20:45 +01:00
" names " : [ " alice " , " bob " ] ,
" city " : " Boston " ,
2017-03-26 08:11:45 +02:00
}
2021-02-12 08:20:45 +01:00
check_dict_only ( keys ) ( " x " , x )
2017-03-26 08:11:45 +02:00
x = {
2021-02-12 08:20:45 +01:00
" names " : [ " alice " , " bob " ] ,
" city " : " Boston " ,
" state " : " Massachusetts " ,
2017-03-26 08:11:45 +02:00
}
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " Unexpected arguments: state " ) :
check_dict_only ( keys ) ( " x " , x )
2017-03-26 08:11:45 +02:00
2019-01-22 19:03:21 +01:00
# Test optional keys
optional_keys = [
2021-02-12 08:20:45 +01:00
( " food " , check_list ( check_string ) ) ,
( " year " , check_int ) ,
2019-01-22 19:03:21 +01:00
]
x = {
2021-02-12 08:20:45 +01:00
" names " : [ " alice " , " bob " ] ,
" city " : " Boston " ,
2021-05-10 07:02:14 +02:00
" food " : [ " Lobster spaghetti " ] ,
2019-01-22 19:03:21 +01:00
}
2021-02-12 08:20:45 +01:00
check_dict ( keys ) ( " x " , x ) # since _allow_only_listed_keys is False
2019-01-22 19:03:21 +01:00
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " Unexpected arguments: food " ) :
check_dict_only ( keys ) ( " x " , x )
2019-01-22 19:03:21 +01:00
2021-02-12 08:20:45 +01:00
check_dict_only ( keys , optional_keys ) ( " x " , x )
2019-01-22 19:03:21 +01:00
x = {
2021-02-12 08:20:45 +01:00
" names " : [ " alice " , " bob " ] ,
" city " : " Boston " ,
2021-05-10 07:02:14 +02:00
" food " : " Lobster spaghetti " ,
2019-01-22 19:03:21 +01:00
}
2020-06-21 02:36:20 +02:00
with self . assertRaisesRegex ( ValidationError , r ' x \ [ " food " \ ] is not a list ' ) :
2021-02-12 08:20:45 +01:00
check_dict_only ( keys , optional_keys ) ( " x " , x )
2019-01-22 19:03:21 +01:00
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.
2020-06-21 02:36:20 +02:00
def check_person ( val : object ) - > Dict [ str , object ] :
try :
2021-02-12 08:19:30 +01:00
return check_dict (
[
2021-02-12 08:20:45 +01:00
( " name " , check_string ) ,
( " age " , check_int ) ,
2021-02-12 08:19:30 +01:00
]
2021-02-12 08:20:45 +01:00
) ( " _ " , val )
2020-06-21 02:36:20 +02:00
except ValidationError :
2021-02-12 08:20:45 +01:00
raise ValidationError ( " This is not a valid person " )
2014-02-07 22:47:30 +01:00
2021-02-12 08:20:45 +01:00
person = { " name " : " King Lear " , " age " : 42 }
2020-06-21 02:36:20 +02:00
check_person ( person )
2014-02-07 22:47:30 +01:00
2021-02-12 08:20:45 +01:00
nonperson = " misconfigured data "
with self . assertRaisesRegex ( ValidationError , r " This is not a valid person " ) :
2020-06-21 02:36:20 +02:00
check_person ( nonperson )
2014-02-07 22:47:30 +01:00
2020-06-20 10:37:43 +02:00
def test_check_union ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = 5
2021-02-12 08:20:45 +01:00
check_union ( [ check_string , check_int ] ) ( " x " , x )
2017-03-05 09:49:05 +01:00
2021-02-12 08:20:45 +01:00
x = " x "
check_union ( [ check_string , check_int ] ) ( " x " , x )
2017-03-05 09:49:05 +01:00
x = [ { } ]
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not an allowed_type " ) :
check_union ( [ check_string , check_int ] ) ( " x " , x )
2017-03-05 09:49:05 +01:00
2017-11-05 10:51:25 +01:00
def test_equals ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = 5
2021-02-12 08:20:45 +01:00
equals ( 5 ) ( " x " , x )
with self . assertRaisesRegex ( ValidationError , r " x != 6 \ (5 is wrong \ ) " ) :
equals ( 6 ) ( " x " , x )
2017-03-05 09:49:05 +01:00
2017-11-05 10:51:25 +01:00
def test_check_none_or ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = 5
2021-02-12 08:20:45 +01:00
check_none_or ( check_int ) ( " x " , x )
2017-03-05 09:49:05 +01:00
x = None
2021-02-12 08:20:45 +01:00
check_none_or ( check_int ) ( " x " , x )
x = " x "
with self . assertRaisesRegex ( ValidationError , r " x is not an integer " ) :
check_none_or ( check_int ) ( " x " , x )
2017-03-05 09:49:05 +01:00
2017-11-05 10:51:25 +01:00
def test_check_url ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
url : Any = " http://127.0.0.1:5002/ "
2021-02-12 08:20:45 +01:00
check_url ( " url " , url )
2017-06-11 09:11:59 +02:00
url = " http://zulip-bots.example.com/ "
2021-02-12 08:20:45 +01:00
check_url ( " url " , url )
2017-06-11 09:11:59 +02:00
url = " http://127.0.0 "
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " url is not a URL " ) :
check_url ( " url " , url )
2018-03-16 06:22:14 +01:00
url = 99.3
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " url is not a string " ) :
check_url ( " url " , url )
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 :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = " string "
2021-02-12 08:20:45 +01:00
check_string_or_int_list ( " x " , x )
2019-06-08 23:19:56 +02:00
x = [ 1 , 2 , 4 ]
2021-02-12 08:20:45 +01:00
check_string_or_int_list ( " x " , x )
2019-06-08 23:19:56 +02:00
x = None
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a string or an integer list " ) :
check_string_or_int_list ( " x " , x )
2019-06-08 23:19:56 +02:00
2021-02-12 08:20:45 +01:00
x = [ 1 , 2 , " 3 " ]
with self . assertRaisesRegex ( ValidationError , r " x \ [2 \ ] is not an integer " ) :
check_string_or_int_list ( " x " , x )
2019-06-08 23:19:56 +02:00
2019-07-13 01:48:04 +02:00
def test_check_string_or_int ( self ) - > None :
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
x : Any = " string "
2021-02-12 08:20:45 +01:00
check_string_or_int ( " x " , x )
2019-07-13 01:48:04 +02:00
x = 1
2021-02-12 08:20:45 +01:00
check_string_or_int ( " x " , x )
2019-07-13 01:48:04 +02:00
x = None
2021-02-12 08:20:45 +01:00
with self . assertRaisesRegex ( ValidationError , r " x is not a string or integer " ) :
check_string_or_int ( " x " , x )
2019-07-13 01:48:04 +02:00
2021-12-15 02:02:38 +01:00
def test_wild_value ( self ) - > None :
x = to_wild_value ( " x " , ' { " a " : 1, " b " : [ " c " , false, null]} ' )
self . assertEqual ( x , x )
self . assertTrue ( x )
self . assertEqual ( len ( x ) , 2 )
self . assertEqual ( list ( x . keys ( ) ) , [ " a " , " b " ] )
self . assertEqual ( list ( x . values ( ) ) , [ 1 , [ " c " , False , None ] ] )
self . assertEqual ( list ( x . items ( ) ) , [ ( " a " , 1 ) , ( " b " , [ " c " , False , None ] ) ] )
self . assertTrue ( " a " in x )
self . assertEqual ( x [ " a " ] , 1 )
self . assertEqual ( x . get ( " a " ) , 1 )
self . assertEqual ( x . get ( " z " ) , None )
self . assertEqual ( x . get ( " z " , x [ " a " ] ) . tame ( check_int ) , 1 )
self . assertEqual ( x [ " a " ] . tame ( check_int ) , 1 )
self . assertEqual ( x [ " b " ] , x [ " b " ] )
self . assertTrue ( x [ " b " ] )
self . assertEqual ( len ( x [ " b " ] ) , 3 )
self . assert_length ( list ( x [ " b " ] ) , 3 )
self . assertEqual ( x [ " b " ] [ 0 ] . tame ( check_string ) , " c " )
self . assertFalse ( x [ " b " ] [ 1 ] )
self . assertFalse ( x [ " b " ] [ 2 ] )
with self . assertRaisesRegex ( ValidationError , r " x is not a string " ) :
x . tame ( check_string )
with self . assertRaisesRegex ( ValidationError , r " x is not a list " ) :
x [ 0 ]
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' z ' \ ] is missing " ) :
x [ " z " ]
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' a ' \ ] is not a list " ) :
x [ " a " ] [ 0 ]
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' a ' \ ] is not a list " ) :
iter ( x [ " a " ] )
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' a ' \ ] is not a dict " ) :
x [ " a " ] [ " a " ]
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' a ' \ ] is not a dict " ) :
x [ " a " ] . get ( " a " )
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' a ' \ ] is not a dict " ) :
2022-11-16 05:50:10 +01:00
_ = " a " in x [ " a " ]
2021-12-15 02:02:38 +01:00
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' a ' \ ] is not a dict " ) :
x [ " a " ] . keys ( )
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' a ' \ ] is not a dict " ) :
x [ " a " ] . values ( )
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' a ' \ ] is not a dict " ) :
x [ " a " ] . items ( )
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' a ' \ ] does not have a length " ) :
len ( x [ " a " ] )
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' b ' \ ] \ [1 \ ] is not a string " ) :
x [ " b " ] [ 1 ] . tame ( check_string )
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' b ' \ ] \ [99 \ ] is missing " ) :
x [ " b " ] [ 99 ]
with self . assertRaisesRegex ( ValidationError , r " x \ [ ' b ' \ ] is not a dict " ) :
x [ " b " ] [ " b " ]
with self . assertRaisesRegex ( InvalidJSONError , r " Malformed JSON " ) :
to_wild_value ( " x " , " invalidjson " )
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 " )
2021-04-02 17:11:25 +02:00
do_deactivate_realm ( get_realm ( " zulip " ) , acting_user = None )
2016-04-21 07:19:08 +02:00
2021-02-12 08:19:30 +01:00
result = self . client_post (
" /json/messages " ,
{
" type " : " private " ,
" content " : " Test message " ,
" 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 ( )
2021-02-12 08:20:45 +01:00
self . login ( " hamlet " )
2016-04-21 07:19:08 +02:00
realm . deactivated = True
realm . save ( )
2021-02-12 08:19:30 +01:00
result = self . client_post (
" /json/messages " ,
{
" type " : " private " ,
" content " : " Test message " ,
" to " : self . example_email ( " othello " ) ,
} ,
)
2021-03-31 13:14:08 +02:00
self . assert_json_error_contains (
2021-07-05 20:28:24 +02:00
result , " This organization has been deactivated " , status_code = 401
2021-03-31 13:14:08 +02:00
)
2016-04-21 07:19:08 +02:00
2021-02-12 08:19:30 +01:00
result = self . api_post (
self . example_user ( " hamlet " ) ,
" /api/v1/messages " ,
{
" type " : " private " ,
" content " : " Test message " ,
" to " : self . example_email ( " othello " ) ,
} ,
)
2021-03-31 13:14:08 +02:00
self . assert_json_error_contains (
result , " This organization has been deactivated " , status_code = 401
)
2016-04-21 07:19:08 +02:00
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 " )
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " hamlet " )
2016-04-21 07:19:08 +02:00
test_password = " abcd1234 "
user_profile . set_password ( test_password )
2020-03-06 18:40:46 +01:00
self . login_user ( user_profile )
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/fetch_api_key " , { " password " : test_password } )
2021-03-31 13:14:08 +02:00
self . assert_json_error_contains (
2021-07-05 20:28:24 +02:00
result , " This organization has been deactivated " , status_code = 401
2021-03-31 13:14:08 +02:00
)
2016-04-21 07:19:08 +02:00
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
"""
2021-04-02 17:11:25 +02:00
do_deactivate_realm ( get_realm ( " zulip " ) , acting_user = None )
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 )
2020-06-10 06:41:04 +02:00
url = f " /api/v1/external/jira?api_key= { api_key } &stream=jira_custom "
2021-02-12 08:20:45 +01:00
data = self . webhook_fixture_data ( " jira " , " created_v2 " )
2021-02-12 08:19:30 +01:00
result = self . client_post ( url , data , content_type = " application/json " )
2021-03-31 13:14:08 +02:00
self . assert_json_error_contains (
2021-07-05 20:28:24 +02:00
result , " This organization has been deactivated " , status_code = 401
2021-03-31 13:14:08 +02:00
)
2016-04-21 18:20:09 +02:00
2021-02-12 08:19:30 +01: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 .
"""
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " hamlet " )
2016-04-22 00:56:39 +02:00
# Verify fails if logged-out
2021-02-12 08:20:45 +01: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
2020-03-06 18:40:46 +01:00
self . login_user ( user_profile )
2021-02-12 08:20:45 +01: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)
2021-02-14 00:03:40 +01:00
change_user_is_active ( user_profile , False )
2021-02-12 08:20:45 +01: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
2021-03-27 05:42:18 +01:00
do_reactivate_user ( user_profile , acting_user = None )
2020-03-06 18:40:46 +01:00
self . login_user ( user_profile )
2021-02-12 08:20:45 +01: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 ( )
2021-02-12 08:20:45 +01:00
result = self . client_get ( " /accounts/accept_terms/ " )
2016-04-22 00:56:39 +02:00
self . assertEqual ( result . status_code , 302 )
2021-02-12 08:19:30 +01:00
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 :
2020-03-06 18:40:46 +01:00
user = self . example_user ( " cordelia " )
self . login_user ( user )
2021-02-12 08:19:30 +01:00
result = self . client_post (
" /json/fetch_api_key " , dict ( password = initial_password ( user . delivery_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 :
2020-03-06 18:40:46 +01:00
user = self . example_user ( " cordelia " )
2021-10-26 09:15:16 +02:00
do_change_user_setting (
user ,
2021-03-01 11:33:24 +01:00
" email_address_visibility " ,
2021-10-26 09:15:16 +02:00
UserProfile . EMAIL_ADDRESS_VISIBILITY_ADMINS ,
2021-03-01 11:33:24 +01:00
acting_user = None ,
2021-02-12 08:19:30 +01:00
)
2019-06-04 00:55:07 +02:00
2020-03-06 18:40:46 +01:00
self . login_user ( user )
2021-02-12 08:19:30 +01:00
result = self . client_post (
" /json/fetch_api_key " , dict ( password = initial_password ( user . delivery_email ) )
)
2019-06-04 00:55:07 +02:00
self . assert_json_success ( result )
2017-11-05 10:51:25 +01:00
def test_fetch_api_key_wrong_password ( self ) - > None :
2021-02-12 08:20:45 +01:00
self . login ( " cordelia " )
result = self . client_post ( " /json/fetch_api_key " , dict ( password = " wrong_password " ) )
2022-01-28 03:59:26 +01:00
self . assert_json_error_contains ( result , " Password is incorrect " )
2016-06-22 01:14:06 +02:00
2021-02-12 08:19:30 +01:00
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
"""
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " hamlet " )
2020-03-06 18:40:46 +01:00
self . login_user ( user_profile )
2022-10-08 21:10:45 +02:00
with self . captureOnCommitCallbacks ( execute = True ) :
do_deactivate_user ( user_profile , acting_user = None )
2016-04-21 18:20:09 +02:00
2021-02-12 08:19:30 +01:00
result = self . client_post (
" /json/messages " ,
{
" type " : " private " ,
" content " : " Test message " ,
" 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
2021-03-27 05:42:18 +01:00
do_reactivate_user ( user_profile , acting_user = None )
2020-03-06 18:40:46 +01:00
self . login_user ( user_profile )
2021-02-14 00:03:40 +01:00
change_user_is_active ( user_profile , False )
2016-04-21 18:20:09 +02:00
2021-02-12 08:19:30 +01:00
result = self . client_post (
" /json/messages " ,
{
" type " : " private " ,
" content " : " Test message " ,
" to " : self . example_email ( " othello " ) ,
} ,
)
2021-07-05 20:28:24 +02:00
self . assert_json_error_contains ( result , " Account is deactivated " , status_code = 401 )
2016-04-21 18:20:09 +02:00
2021-02-12 08:19:30 +01:00
result = self . api_post (
self . example_user ( " hamlet " ) ,
" /api/v1/messages " ,
{
" type " : " private " ,
" content " : " Test message " ,
" 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
"""
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " hamlet " )
2020-03-12 14:17:25 +01:00
email = user_profile . delivery_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
2020-03-06 18:40:46 +01:00
self . login_by_email ( email , password = test_password )
2021-02-14 00:03:40 +01:00
change_user_is_active ( user_profile , False )
2016-07-28 00:30:22 +02:00
result = self . client_post ( " /json/fetch_api_key " , { " password " : test_password } )
2021-07-05 20:28:24 +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_login_deactivated_user ( self ) - > None :
2016-04-21 18:20:09 +02:00
"""
logging in fails with an inactive user
"""
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " hamlet " )
2021-03-27 06:02:12 +01:00
do_deactivate_user ( user_profile , acting_user = None )
2016-04-21 18:20:09 +02:00
2021-08-23 15:14:05 +02:00
result = self . login_with_return ( user_profile . delivery_email )
self . assert_in_response (
2021-08-23 15:14:05 +02:00
f " Your account { user_profile . delivery_email } has been deactivated. " , result
2021-08-23 15:14:05 +02:00
)
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
"""
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " hamlet " )
2017-06-27 13:10:16 +02:00
user_profile . is_mirror_dummy = True
user_profile . save ( )
2020-03-12 14:17:25 +01:00
password = initial_password ( user_profile . delivery_email )
2017-06-27 13:10:16 +02:00
request = mock . MagicMock ( )
2021-02-12 08:20:45 +01:00
request . get_host . return_value = " zulip.testserver "
2017-06-27 13:10:16 +02:00
2020-03-12 14:17:25 +01:00
payload = dict (
username = user_profile . delivery_email ,
password = password ,
)
2017-06-27 13:10:16 +02:00
# Test a mirror-dummy active user.
2020-03-12 14:17:25 +01:00
form = OurAuthenticationForm ( request , payload )
2021-02-12 08:20:45 +01:00
with self . settings ( AUTHENTICATION_BACKENDS = ( " zproject.backends.EmailAuthBackend " , ) ) :
2017-06-27 13:10:16 +02:00
self . assertTrue ( form . is_valid ( ) )
# Test a mirror-dummy deactivated user.
2021-03-27 06:02:12 +01:00
do_deactivate_user ( user_profile , acting_user = None )
2017-06-27 13:10:16 +02:00
user_profile . save ( )
2020-03-12 14:17:25 +01:00
form = OurAuthenticationForm ( request , payload )
2021-02-12 08:20:45 +01:00
with self . settings ( AUTHENTICATION_BACKENDS = ( " zproject.backends.EmailAuthBackend " , ) ) :
2017-06-27 13:10:16 +02:00
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 ( )
2020-03-12 14:17:25 +01:00
form = OurAuthenticationForm ( request , payload )
2021-02-12 08:20:45 +01:00
with self . settings ( AUTHENTICATION_BACKENDS = ( " zproject.backends.EmailAuthBackend " , ) ) :
2017-06-27 13:10:16 +02:00
self . assertFalse ( form . is_valid ( ) )
2021-08-23 15:14:05 +02:00
self . assertIn (
2021-09-22 06:10:00 +02:00
f " Your account { user_profile . delivery_email } has been deactivated " ,
2021-08-23 15:14:05 +02:00
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
"""
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " hamlet " )
2021-03-27 06:02:12 +01:00
do_deactivate_user ( user_profile , acting_user = None )
2016-04-21 18:20:09 +02:00
2018-08-01 10:53:40 +02:00
api_key = get_api_key ( user_profile )
2020-06-10 06:41:04 +02:00
url = f " /api/v1/external/jira?api_key= { api_key } &stream=jira_custom "
2021-02-12 08:20:45 +01:00
data = self . webhook_fixture_data ( " jira " , " created_v2 " )
2021-02-12 08:19:30 +01:00
result = self . client_post ( url , data , content_type = " application/json " )
2021-07-05 20:28:24 +02:00
self . assert_json_error_contains ( result , " Account is deactivated " , status_code = 401 )
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 test_webhook_bot_permissions ( self ) - > None :
2021-02-12 08:20:45 +01:00
webhook_bot = self . example_user ( " webhook_bot " )
othello = self . example_user ( " othello " )
2020-03-12 14:17:25 +01:00
payload = dict (
2021-02-12 08:20:45 +01:00
type = " private " ,
content = " Test message " ,
2022-09-13 08:39:44 +02:00
to = orjson . dumps ( [ othello . email ] ) . decode ( ) ,
2020-03-12 14:17:25 +01:00
)
result = self . api_post ( webhook_bot , " /api/v1/messages " , payload )
2017-08-22 19:01:17 +02:00
self . assert_json_success ( result )
post_params = { " anchor " : 1 , " num_before " : 1 , " num_after " : 1 }
2020-03-10 11:48:26 +01:00
result = self . api_get ( webhook_bot , " /api/v1/messages " , dict ( post_params ) )
2021-02-12 08:19:30 +01:00
self . assert_json_error (
2021-02-12 08:20:45 +01:00
result , " This API is not available to incoming webhook bots. " , status_code = 401
2021-02-12 08:19:30 +01:00
)
2017-08-22 19:01:17 +02:00
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 ( )
2021-02-12 08:20:45 +01: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 ) :
2021-02-12 08:19:30 +01:00
validate_api_key (
2021-02-12 08:20:45 +01:00
HostRequestMock ( ) , " email@doesnotexist.com " , " VIzRVw2CspUOnEm9Yu5vQiQtJNkvETkp "
2021-02-12 08:19:30 +01:00
)
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 ) :
2021-02-12 08:20:45 +01: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 :
2021-02-14 00:03:40 +01:00
change_user_is_active ( self . default_bot , False )
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 . default_bot )
validate_api_key ( HostRequestMock ( ) , self . default_bot . email , api_key )
2021-02-14 00:03:40 +01:00
change_user_is_active ( self . default_bot , True )
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_unset ( self ) - > None :
2020-07-18 17:37:12 +02:00
with self . assertRaises ( JsonableError ) , self . assertLogs ( level = " WARNING " ) as root_warn_log :
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 )
2021-02-12 08:19:30 +01:00
self . assertEqual (
root_warn_log . output ,
[
2021-02-12 08:20:45 +01:00
" WARNING:root:User webhook-bot@zulip.com (zulip) attempted to access API on wrong subdomain () "
2021-02-12 08:19:30 +01:00
] ,
)
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 )
2021-02-12 08:19:30 +01:00
profile = validate_api_key (
HostRequestMock ( host = " zulip.testserver " ) ,
self . webhook_bot . email ,
api_key ,
allow_webhook_access = 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 )
2021-02-12 08:19:30 +01:00
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 )
2020-07-15 12:46:41 +02:00
with self . assertLogs ( level = " WARNING " ) as m :
2021-02-12 08:19:30 +01:00
with self . assertRaisesRegex (
JsonableError , " Account is not associated with this subdomain "
) :
validate_api_key (
HostRequestMock ( host = settings . EXTERNAL_HOST ) ,
self . default_bot . email ,
api_key ,
)
2020-07-15 12:46:41 +02:00
self . assertEqual (
m . output ,
2021-02-12 08:19:30 +01:00
[
" WARNING:root:User {} ( {} ) attempted to access API on wrong subdomain ( {} ) " . format (
self . default_bot . email , " zulip " , " "
)
] ,
2020-07-15 12:46:41 +02:00
)
2017-10-02 22:37:20 +02:00
2020-07-15 12:46:41 +02:00
with self . assertLogs ( level = " WARNING " ) as m :
2021-02-12 08:19:30 +01:00
with self . assertRaisesRegex (
JsonableError , " Account is not associated with this subdomain "
) :
validate_api_key (
2021-02-12 08:20:45 +01:00
HostRequestMock ( host = " acme. " + settings . EXTERNAL_HOST ) ,
2021-02-12 08:19:30 +01:00
self . default_bot . email ,
api_key ,
)
2020-07-15 12:46:41 +02:00
self . assertEqual (
m . output ,
2021-02-12 08:19:30 +01:00
[
" WARNING:root:User {} ( {} ) attempted to access API on wrong subdomain ( {} ) " . format (
self . default_bot . email , " zulip " , " acme "
)
] ,
2020-07-15 12:46:41 +02:00
)
2016-10-12 10:43:20 +02:00
2021-02-12 08:19:30 +01:00
2020-07-01 04:19:54 +02:00
class TestInternalNotifyView ( ZulipTestCase ) :
2021-02-12 08:20:45 +01:00
BORING_RESULT = " boring "
2016-07-09 12:02:24 +02:00
2017-11-05 10:51:25 +01:00
def internal_notify ( self , is_tornado : bool , req : HttpRequest ) - > HttpResponse :
2021-07-26 17:17:16 +02:00
boring_view = lambda req : json_response ( msg = 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 :
2021-02-12 08:20:45 +01:00
secret = " random "
2021-07-09 17:40:21 +02:00
request = HostRequestMock (
post_data = dict ( secret = secret ) ,
meta_data = dict ( REMOTE_ADDR = " 127.0.0.1 " ) ,
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
)
2016-07-09 12:02:24 +02:00
2017-04-18 18:56:19 +02:00
with self . settings ( SHARED_SECRET = secret ) :
2021-07-09 13:25:36 +02:00
self . assertTrue ( authenticate_notify ( request ) )
2021-07-26 17:17:16 +02:00
self . assertEqual (
orjson . loads ( self . internal_notify ( False , request ) . content ) . get ( " msg " ) ,
self . BORING_RESULT ,
)
2021-08-21 19:24:20 +02:00
self . assertEqual ( RequestNotes . get_notes ( request ) . requestor_for_logs , " internal " )
2017-04-18 18:56:19 +02:00
with self . assertRaises ( RuntimeError ) :
2021-07-09 13:25:36 +02:00
self . internal_notify ( True , request )
2017-04-18 18:56:19 +02:00
2022-05-12 08:28:00 +02:00
request = HostRequestMock (
post_data = dict ( secret = secret ) ,
meta_data = dict ( REMOTE_ADDR = " 127.0.0.1 " ) ,
2022-06-24 09:18:14 +02:00
tornado_handler = dummy_handler ,
2022-05-12 08:28:00 +02:00
)
2016-07-09 12:02:24 +02:00
with self . settings ( SHARED_SECRET = secret ) :
2021-07-09 13:25:36 +02:00
self . assertTrue ( authenticate_notify ( request ) )
2021-07-26 17:17:16 +02:00
self . assertEqual (
orjson . loads ( self . internal_notify ( True , request ) . content ) . get ( " msg " ) ,
self . BORING_RESULT ,
)
2021-08-21 19:24:20 +02:00
self . assertEqual ( RequestNotes . get_notes ( request ) . requestor_for_logs , " internal " )
2016-07-09 12:02:24 +02:00
2017-04-18 18:56:19 +02:00
with self . assertRaises ( RuntimeError ) :
2021-07-09 13:25:36 +02:00
self . internal_notify ( False , request )
2017-04-18 18:56:19 +02:00
2017-11-05 10:51:25 +01:00
def test_internal_requests_with_broken_secret ( self ) - > None :
2022-07-13 19:11:20 +02:00
request = HostRequestMock (
post_data = dict ( data = " something " ) ,
meta_data = dict ( REMOTE_ADDR = " 127.0.0.1 " ) ,
)
with self . settings ( SHARED_SECRET = " random " ) :
2022-07-15 00:00:31 +02:00
with self . assertRaises ( RequestVariableMissingError ) as context :
2022-07-13 19:11:20 +02:00
self . internal_notify ( True , request )
2022-07-15 00:00:31 +02:00
self . assertEqual ( context . exception . http_status_code , 400 )
with self . settings ( SHARED_SECRET = None ) :
with self . assertRaises ( RequestVariableMissingError ) as context :
self . internal_notify ( True , request )
self . assertEqual ( context . exception . http_status_code , 400 )
2022-07-13 19:11:20 +02:00
2021-02-12 08:20:45 +01:00
secret = " random "
2021-07-09 17:40:21 +02:00
request = HostRequestMock (
post_data = dict ( secret = secret ) ,
meta_data = dict ( REMOTE_ADDR = " 127.0.0.1 " ) ,
2017-01-24 06:34:26 +01:00
)
2016-07-09 12:02:24 +02:00
2021-02-12 08:20:45 +01:00
with self . settings ( SHARED_SECRET = " broken " ) :
2021-07-09 17:40:21 +02:00
self . assertFalse ( authenticate_notify ( request ) )
2022-07-28 17:17:55 +02:00
with self . assertRaises ( AccessDeniedError ) as access_denied_error :
2021-07-09 17:40:21 +02:00
self . internal_notify ( True , request )
2022-07-28 17:17:55 +02:00
self . assertEqual ( access_denied_error . exception . http_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 :
2021-02-12 08:20:45 +01:00
secret = " random "
2021-07-09 17:40:21 +02:00
request = HostRequestMock (
post_data = dict ( secret = secret ) ,
meta_data = 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 ) :
2021-07-09 17:40:21 +02:00
self . assertFalse ( authenticate_notify ( request ) )
2021-07-04 08:45:34 +02:00
with self . assertRaises ( AccessDeniedError ) as context :
2021-07-09 17:40:21 +02:00
self . internal_notify ( True , request )
2021-07-04 08:45:34 +02:00
self . assertEqual ( context . exception . http_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 :
2021-02-12 08:20:45 +01: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 " ) )
2016-07-09 20:37:09 +02:00
2021-02-12 08:19:30 +01:00
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 :
2021-02-12 08:20:45 +01:00
default_bot = self . example_user ( " default_bot " )
2020-03-10 11:48:26 +01:00
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-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 " ,
2017-04-15 20:51:51 +02:00
]
2017-04-16 22:10:56 +02:00
for endpoint in post_endpoints :
2020-03-10 11:48:26 +01:00
result = self . api_post ( default_bot , 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 " ,
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
" /api/v1/users/me/profile_data " ,
2017-04-16 22:10:56 +02:00
]
for endpoint in patch_endpoints :
2020-03-10 11:48:26 +01:00
result = self . api_patch ( default_bot , 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 :
2020-03-10 11:48:26 +01:00
result = self . api_delete ( default_bot , endpoint )
2017-06-19 22:09:35 +02:00
self . assert_json_error ( result , " This endpoint does not accept bot requests. " )
2021-02-12 08:19:30 +01:00
2020-08-22 13:52:39 +02:00
class TestAuthenticatedRequirePostDecorator ( ZulipTestCase ) :
def test_authenticated_html_post_view_with_get_request ( self ) - > None :
2021-02-12 08:20:45 +01:00
self . login ( " hamlet " )
2020-10-29 20:21:18 +01:00
with self . assertLogs ( level = " WARNING " ) as mock_warning :
2021-02-12 08:20:45 +01:00
result = self . client_get ( r " /accounts/register/ " , { " stream " : " Verona " } )
2020-08-22 13:52:39 +02:00
self . assertEqual ( result . status_code , 405 )
2021-02-12 08:19:30 +01:00
self . assertEqual (
mock_warning . output , [ " WARNING:root:Method Not Allowed (GET): /accounts/register/ " ]
)
2020-08-22 13:52:39 +02:00
2020-10-29 20:21:18 +01:00
with self . assertLogs ( level = " WARNING " ) as mock_warning :
2021-02-12 08:20:45 +01:00
result = self . client_get ( r " /accounts/logout/ " , { " stream " : " Verona " } )
2020-08-22 13:52:39 +02:00
self . assertEqual ( result . status_code , 405 )
2021-02-12 08:19:30 +01:00
self . assertEqual (
mock_warning . output , [ " WARNING:root:Method Not Allowed (GET): /accounts/logout/ " ]
)
2020-08-22 13:52:39 +02:00
def test_authenticated_json_post_view_with_get_request ( self ) - > None :
2021-02-12 08:20:45 +01:00
self . login ( " hamlet " )
2020-10-29 20:21:18 +01:00
with self . assertLogs ( level = " WARNING " ) as mock_warning :
2021-02-12 08:20:45 +01:00
result = self . client_get ( r " /api/v1/dev_fetch_api_key " , { " stream " : " Verona " } )
2020-08-22 13:52:39 +02:00
self . assertEqual ( result . status_code , 405 )
2021-02-12 08:19:30 +01:00
self . assertEqual (
mock_warning . output ,
[ " WARNING:root:Method Not Allowed (GET): /api/v1/dev_fetch_api_key " ] ,
)
2020-08-22 13:52:39 +02:00
2020-10-29 20:21:18 +01:00
with self . assertLogs ( level = " WARNING " ) as mock_warning :
2021-02-12 08:20:45 +01:00
result = self . client_get ( r " /json/remotes/server/register " , { " stream " : " Verona " } )
2020-08-22 13:52:39 +02:00
self . assertEqual ( result . status_code , 405 )
2021-02-12 08:19:30 +01:00
self . assertEqual (
mock_warning . output ,
[ " WARNING:root:Method Not Allowed (GET): /json/remotes/server/register " ] ,
)
2020-08-22 13:52:39 +02:00
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 :
2021-02-12 08:20:45 +01:00
user = self . example_user ( " hamlet " )
2020-03-06 18:40:46 +01:00
self . login_user ( user )
response = self . _do_test ( user )
2023-02-14 19:46:36 +01:00
self . assert_json_success ( response )
def test_authenticated_json_post_view_if_user_not_logged_in ( self ) - > None :
user = self . example_user ( " hamlet " )
self . assert_json_error_contains (
self . _do_test ( user ) ,
" Not logged in: API authentication or user session required " ,
status_code = 401 ,
)
2016-05-18 20:35:35 +02:00
2018-04-26 07:28:31 +02:00
def test_authenticated_json_post_view_with_get_request ( self ) - > None :
2021-02-12 08:20:45 +01:00
self . login ( " hamlet " )
2020-07-15 12:46:41 +02:00
with self . assertLogs ( level = " WARNING " ) as m :
2021-02-12 08:20:45 +01:00
result = self . client_get ( r " /json/subscriptions/exists " , { " stream " : " Verona " } )
2018-04-26 07:28:31 +02:00
self . assertEqual ( result . status_code , 405 )
2021-02-12 08:19:30 +01:00
self . assertEqual (
m . output ,
[
" WARNING:root:Method Not Allowed ( {} ): {} " . format (
" GET " , " /json/subscriptions/exists "
)
] ,
)
2018-04-26 07:28:31 +02:00
2017-11-05 10:51:25 +01:00
def test_authenticated_json_post_view_if_subdomain_is_invalid ( self ) - > None :
2021-02-12 08:20:45 +01:00
user = self . example_user ( " hamlet " )
2020-03-12 14:17:25 +01:00
email = user . delivery_email
2020-03-06 18:40:46 +01:00
self . login_user ( user )
2021-02-12 08:19:30 +01:00
with self . assertLogs ( level = " WARNING " ) as m , mock . patch (
2021-02-12 08:20:45 +01:00
" zerver.decorator.get_subdomain " , return_value = " "
2021-02-12 08:19:30 +01:00
) :
self . assert_json_error_contains (
self . _do_test ( user ) , " Account is not associated with this subdomain "
)
2020-07-15 12:46:41 +02:00
self . assertEqual (
m . output ,
2021-02-12 08:19:30 +01:00
[
" WARNING:root:User {} ( {} ) attempted to access API on wrong subdomain ( {} ) " . format (
2021-02-12 08:20:45 +01:00
email , " zulip " , " "
2021-02-12 08:19:30 +01:00
) ,
" WARNING:root:User {} ( {} ) attempted to access API on wrong subdomain ( {} ) " . format (
2021-02-12 08:20:45 +01:00
email , " zulip " , " "
2021-02-12 08:19:30 +01:00
) ,
] ,
2020-07-15 12:46:41 +02:00
)
2017-10-02 22:37:20 +02:00
2021-02-12 08:19:30 +01:00
with self . assertLogs ( level = " WARNING " ) as m , mock . patch (
2021-02-12 08:20:45 +01:00
" zerver.decorator.get_subdomain " , return_value = " acme "
2021-02-12 08:19:30 +01:00
) :
self . assert_json_error_contains (
self . _do_test ( user ) , " Account is not associated with this subdomain "
)
2020-07-15 12:46:41 +02:00
self . assertEqual (
m . output ,
2021-02-12 08:19:30 +01:00
[
" WARNING:root:User {} ( {} ) attempted to access API on wrong subdomain ( {} ) " . format (
2021-02-12 08:20:45 +01:00
email , " zulip " , " acme "
2021-02-12 08:19:30 +01:00
) ,
" WARNING:root:User {} ( {} ) attempted to access API on wrong subdomain ( {} ) " . format (
2021-02-12 08:20:45 +01:00
email , " zulip " , " acme "
2021-02-12 08:19:30 +01:00
) ,
] ,
2020-07-15 12:46:41 +02:00
)
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 :
2021-02-12 08:20:45 +01:00
bot = self . example_user ( " webhook_bot " )
bot . set_password ( " test " )
2020-03-06 18:40:46 +01:00
bot . save ( )
2021-02-12 08:20:45 +01:00
self . login_by_email ( bot . email , password = " test " )
2020-03-06 18:40:46 +01:00
self . assert_json_error_contains ( self . _do_test ( bot ) , " Webhook bots can only access webhooks " )
2016-05-18 20:35:35 +02:00
2017-11-05 10:51:25 +01:00
def test_authenticated_json_post_view_if_user_is_not_active ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " hamlet " )
2020-03-06 18:40:46 +01:00
self . login_user ( user_profile )
2016-05-18 20:35:35 +02:00
# we deactivate user manually because do_deactivate_user removes user session
2021-02-14 00:03:40 +01:00
change_user_is_active ( user_profile , False )
2021-03-31 12:00:56 +02:00
self . assert_json_error_contains (
2021-07-05 20:28:24 +02:00
self . _do_test ( user_profile ) , " Account is deactivated " , status_code = 401
2021-03-31 12:00:56 +02:00
)
2021-03-27 05:42:18 +01:00
do_reactivate_user ( user_profile , acting_user = None )
2016-05-18 20:35:35 +02:00
2017-11-05 10:51:25 +01:00
def test_authenticated_json_post_view_if_user_realm_is_deactivated ( self ) - > None :
2021-02-12 08:20:45 +01:00
user_profile = self . example_user ( " hamlet " )
2020-03-06 18:40:46 +01:00
self . login_user ( user_profile )
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 ( )
2021-02-12 08:19:30 +01:00
self . assert_json_error_contains (
2021-03-31 13:14:08 +02:00
self . _do_test ( user_profile ) ,
" This organization has been deactivated " ,
2021-07-05 20:28:24 +02:00
status_code = 401 ,
2021-02-12 08:19:30 +01:00
)
2016-05-18 20:35:35 +02:00
do_reactivate_realm ( user_profile . realm )
2022-06-08 04:52:09 +02:00
def _do_test ( self , user : UserProfile ) - > " TestHttpResponse " :
2018-01-05 21:25:33 +01:00
stream_name = " stream name "
2020-06-17 23:49:33 +02:00
self . common_subscribe_to_streams ( user , [ stream_name ] , allow_fail = True )
2023-02-14 19:46:36 +01:00
data = { " stream " : stream_name }
2021-02-12 08:20:45 +01:00
return self . client_post ( " /json/subscriptions/exists " , data )
2016-10-12 10:43:20 +02:00
2021-02-12 08:19:30 +01:00
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 :
2021-02-12 08:20:45 +01:00
user = self . example_user ( " hamlet " )
2020-03-12 14:17:25 +01:00
email = user . delivery_email
2020-03-06 18:40:46 +01:00
self . login_user ( user )
2016-10-12 10:43:20 +02:00
2021-02-12 08:19:30 +01:00
with self . assertLogs ( level = " WARNING " ) as m , mock . patch (
2021-02-12 08:20:45 +01:00
" zerver.decorator.get_subdomain " , return_value = " "
2021-02-12 08:19:30 +01:00
) :
self . assert_json_error_contains (
self . _do_test ( email ) , " Account is not associated with this subdomain "
)
2020-07-15 12:46:41 +02:00
self . assertEqual (
m . output ,
2021-02-12 08:19:30 +01:00
[
" WARNING:root:User {} ( {} ) attempted to access API on wrong subdomain ( {} ) " . format (
email , " zulip " , " "
)
] ,
2020-07-15 12:46:41 +02:00
)
2017-10-02 22:37:20 +02:00
2021-02-12 08:19:30 +01:00
with self . assertLogs ( level = " WARNING " ) as m , mock . patch (
2021-02-12 08:20:45 +01:00
" zerver.decorator.get_subdomain " , return_value = " acme "
2021-02-12 08:19:30 +01:00
) :
self . assert_json_error_contains (
self . _do_test ( email ) , " Account is not associated with this subdomain "
)
2020-07-15 12:46:41 +02:00
self . assertEqual (
m . output ,
2021-02-12 08:19:30 +01:00
[
" WARNING:root:User {} ( {} ) attempted to access API on wrong subdomain ( {} ) " . format (
email , " zulip " , " acme "
)
] ,
2020-07-15 12:46:41 +02:00
)
2016-10-12 10:43:20 +02:00
2022-06-08 04:52:09 +02:00
def _do_test ( self , user_email : str ) - > " TestHttpResponse " :
2017-10-09 22:48:17 +02:00
data = { " password " : initial_password ( user_email ) }
2021-02-12 08:20:45 +01:00
return self . client_post ( r " /accounts/webathena_kerberos_login/ " , data )
2016-10-12 10:43:20 +02:00
2021-02-12 08:19:30 +01:00
2022-08-01 20:46:23 +02:00
class TestPublicJsonViewDecorator ( ZulipTestCase ) :
def test_access_public_json_view_when_logged_in ( self ) - > None :
hamlet = self . example_user ( " hamlet " )
@public_json_view
def public_view (
request : HttpRequest , maybe_user_profile : Union [ UserProfile , AnonymousUser ]
) - > HttpResponse :
self . assertEqual ( maybe_user_profile , hamlet )
return json_success ( request )
result = public_view ( HostRequestMock ( host = " zulip.testserver " , user_profile = hamlet ) )
self . assert_json_success ( result )
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 :
2021-02-12 08:20:45 +01:00
self . login ( " hamlet " )
2016-10-12 10:43:20 +02:00
2021-02-12 08:20:45 +01:00
with mock . patch ( " zerver.decorator.get_subdomain " , return_value = " zulip " ) :
result = self . client_get ( " /accounts/accept_terms/ " )
2017-10-02 22:37:20 +02:00
self . assertEqual ( result . status_code , 200 )
2016-10-12 10:43:20 +02:00
2021-02-12 08:20:45 +01:00
with mock . patch ( " zerver.decorator.get_subdomain " , return_value = " " ) :
result = self . client_get ( " /accounts/accept_terms/ " )
2017-10-02 22:37:20 +02:00
self . assertEqual ( result . status_code , 302 )
2016-10-12 10:43:20 +02:00
2021-02-12 08:20:45 +01:00
with mock . patch ( " zerver.decorator.get_subdomain " , return_value = " acme " ) :
result = self . client_get ( " /accounts/accept_terms/ " )
2017-10-02 22:37:20 +02:00
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 :
2021-02-12 08:20:45 +01:00
return HttpResponse ( " Success " )
2017-07-12 10:16:02 +02:00
2021-07-09 17:40:21 +02:00
meta_data = {
" SERVER_NAME " : " localhost " ,
" SERVER_PORT " : 80 ,
" PATH_INFO " : " " ,
}
user = hamlet = self . example_user ( " hamlet " )
2020-03-06 18:40:46 +01:00
self . login_user ( hamlet )
2021-07-09 17:40:21 +02:00
request = HostRequestMock (
client_name = " " , user_profile = user , meta_data = meta_data , host = " zulip.testserver "
)
2017-07-12 10:16:02 +02:00
request . session = self . client . session
2022-07-08 23:06:28 +02:00
with mock . patch ( " zerver.lib.users.is_verified " , lambda _ : False ) :
response = test_view ( request )
self . assertEqual ( response . content . decode ( ) , " Success " )
2017-07-12 10:16:02 +02:00
with self . settings ( TWO_FACTOR_AUTHENTICATION_ENABLED = True ) :
2021-07-09 17:40:21 +02:00
user = hamlet = self . example_user ( " hamlet " )
2020-03-06 18:40:46 +01:00
self . login_user ( hamlet )
2021-07-09 17:40:21 +02:00
request = HostRequestMock (
client_name = " " , user_profile = user , meta_data = meta_data , host = " zulip.testserver "
)
2017-07-12 10:16:02 +02:00
request . session = self . client . session
2021-07-09 17:40:21 +02:00
assert type ( request . user ) is UserProfile
2017-07-12 10:16:02 +02:00
self . create_default_device ( request . user )
2022-07-08 23:06:28 +02:00
with mock . patch ( " zerver.lib.users.is_verified " , lambda _ : False ) :
response = test_view ( request )
2017-07-12 10:16:02 +02:00
2022-06-26 20:41:49 +02:00
self . assertEqual ( response . status_code , 302 )
2017-07-12 10:16:02 +02:00
2022-06-26 20:41:49 +02:00
response_url = response [ " Location " ] . split ( " ? " ) [ 0 ]
2017-07-12 10:16:02 +02:00
self . assertEqual ( response_url , settings . HOME_NOT_LOGGED_IN )
def test_2fa_success ( self ) - > None :
@zulip_login_required
def test_view ( request : HttpRequest ) - > HttpResponse :
2021-02-12 08:20:45 +01:00
return HttpResponse ( " Success " )
2017-07-12 10:16:02 +02:00
with self . settings ( TWO_FACTOR_AUTHENTICATION_ENABLED = True ) :
2021-07-09 17:40:21 +02:00
meta_data = {
" SERVER_NAME " : " localhost " ,
" SERVER_PORT " : 80 ,
" PATH_INFO " : " " ,
}
user = hamlet = self . example_user ( " hamlet " )
2020-03-06 18:40:46 +01:00
self . login_user ( hamlet )
2021-07-09 17:40:21 +02:00
request = HostRequestMock (
client_name = " " , user_profile = user , meta_data = meta_data , host = " zulip.testserver "
)
2017-07-12 10:16:02 +02:00
request . session = self . client . session
2021-07-09 17:40:21 +02:00
assert type ( request . user ) is UserProfile
2017-07-12 10:16:02 +02:00
self . create_default_device ( request . user )
2022-07-08 23:06:28 +02:00
with mock . patch ( " zerver.lib.users.is_verified " , lambda _ : True ) :
response = test_view ( request )
self . assertEqual ( response . content . decode ( ) , " Success " )
2017-07-12 10:16:02 +02:00
2022-07-08 22:07:15 +02:00
def test_otp_not_authenticated ( self ) - > None :
2022-07-11 16:33:02 +02:00
@zulip_otp_required_if_logged_in ( )
2022-07-08 22:07:15 +02:00
def test_view ( request : HttpRequest ) - > HttpResponse :
return HttpResponse ( " Success " )
with self . settings ( TWO_FACTOR_AUTHENTICATION_ENABLED = True ) :
2022-07-18 23:14:42 +02:00
request = HostRequestMock ( )
2022-07-08 22:07:15 +02:00
response = test_view ( request )
self . assertEqual ( response . content . decode ( ) , " Success " )
2021-02-12 08:19:30 +01:00
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 :
2021-05-03 18:18:23 +02:00
realm_owner = self . example_user ( " desdemona " )
self . login_user ( realm_owner )
2016-12-14 06:02:50 +01:00
2021-02-12 08:20:45 +01:00
result = self . client_get ( " /activity " )
2016-12-14 06:02:50 +01:00
self . assertEqual ( result . status_code , 302 )
2021-05-03 18:18:23 +02:00
server_admin = self . example_user ( " iago " )
self . login_user ( server_admin )
self . assertEqual ( server_admin . is_staff , True )
2016-12-14 06:02:50 +01:00
2021-02-12 08:20:45 +01:00
result = self . client_get ( " /activity " )
2016-12-14 06:02:50 +01:00
self . assertEqual ( result . status_code , 200 )
2018-05-04 19:14:29 +02:00
def test_require_non_guest_user_decorator ( self ) - > None :
2021-02-12 08:20:45 +01:00
guest_user = self . example_user ( " polonius " )
2020-03-06 18:40:46 +01:00
self . login_user ( guest_user )
2020-06-17 23:49:33 +02:00
result = self . common_subscribe_to_streams ( guest_user , [ " Denmark " ] , allow_fail = True )
2018-05-04 19:14:29 +02:00
self . assert_json_error ( result , " Not allowed for guest users " )
2021-02-12 08:20:45 +01:00
outgoing_webhook_bot = self . example_user ( " outgoing_webhook_bot " )
result = self . api_get ( outgoing_webhook_bot , " /api/v1/bots " )
2018-05-04 19:14:29 +02:00
self . assert_json_error ( result , " This endpoint does not accept bot requests. " )
2021-02-12 08:20:45 +01:00
guest_user = self . example_user ( " polonius " )
2020-03-06 18:40:46 +01:00
self . login_user ( guest_user )
2021-02-12 08:20:45 +01:00
result = self . client_get ( " /json/bots " )
2018-05-04 19:14:29 +02:00
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 :
2021-07-26 17:17:16 +02:00
class HeadRequest ( HostRequestMock ) :
def __init__ ( self , * args : Any , * * kwargs : Any ) - > None :
super ( ) . __init__ ( * args , * * kwargs )
self . method = " HEAD "
2016-11-15 17:20:22 +01:00
request = HeadRequest ( )
@return_success_on_head_request
2017-11-05 10:51:25 +01:00
def test_function ( request : HttpRequest ) - > HttpResponse :
2021-02-12 08:20:45 +01:00
return json_response ( msg = " from_test_function " ) # nocoverage. isn't meant to be called
2016-11-15 17:20:22 +01:00
response = test_function ( request )
2023-01-06 13:13:24 +01:00
self . assertEqual ( response . status_code , 200 )
self . assertEqual ( response . content , b " " )
2016-11-15 17:20:22 +01:00
2017-11-17 07:00:53 +01:00
def test_returns_normal_response_if_request_method_is_not_head ( self ) - > None :
2021-07-26 17:17:16 +02:00
class HeadRequest ( HostRequestMock ) :
def __init__ ( self , * args : Any , * * kwargs : Any ) - > None :
super ( ) . __init__ ( * args , * * kwargs )
self . 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 :
2021-02-12 08:20:45 +01:00
return json_response ( msg = " from_test_function " )
2016-11-15 17:20:22 +01:00
2019-01-31 14:32:37 +01:00
response = test_function ( request )
2021-02-12 08:20:45 +01:00
self . assertEqual ( orjson . loads ( response . content ) . get ( " msg " ) , " from_test_function " )
2017-03-05 09:26:07 +01:00
2021-02-12 08:19:30 +01:00
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 :
2021-02-12 08:20:45 +01:00
self . login ( " hamlet " )
result = self . client_patch ( " /json/users " )
2017-03-05 09:26:07 +01:00
self . assertEqual ( result . status_code , 405 )
2021-02-12 08:20:45 +01:00
self . assert_in_response ( " Method Not Allowed " , result )
2017-03-05 09:31:17 +01:00
2022-08-01 22:55:13 +02:00
with self . settings ( ZILENCER_ENABLED = True ) :
result = self . client_patch ( " /json/remotes/push/register " )
self . assertEqual ( result . status_code , 405 )
self . assert_in_response ( " Method Not Allowed " , result )
2017-11-05 10:51:25 +01:00
def test_options_method ( self ) - > None :
2021-02-12 08:20:45 +01:00
self . login ( " hamlet " )
result = self . client_options ( " /json/users " )
2017-03-05 09:31:17 +01:00
self . assertEqual ( result . status_code , 204 )
2021-02-12 08:20:45 +01:00
self . assertEqual ( str ( result [ " Allow " ] ) , " GET, HEAD, POST " )
2017-03-05 09:31:17 +01:00
2021-02-12 08:20:45 +01:00
result = self . client_options ( " /json/streams/15 " )
2017-03-05 09:31:17 +01:00
self . assertEqual ( result . status_code , 204 )
2022-05-18 13:54:35 +02:00
self . assertEqual ( str ( result [ " Allow " ] ) , " DELETE, GET, HEAD, PATCH " )
2017-03-05 09:39:36 +01:00
2017-11-05 10:51:25 +01:00
def test_http_accept_redirect ( self ) - > None :
2021-02-12 08:20:45 +01:00
result = self . client_get ( " /json/users " , HTTP_ACCEPT = " text/html " )
2017-03-05 09:39:36 +01:00
self . assertEqual ( result . status_code , 302 )
2022-05-12 06:27:31 +02:00
self . assertTrue ( result [ " Location " ] . endswith ( " /login/?next= %2F json %2F users " ) )
2017-09-16 08:24:17 +02:00
2021-02-12 08:19:30 +01:00
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. """
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
user_agents_parsed : Dict [ str , int ] = defaultdict ( int )
2021-02-12 08:19:30 +01:00
user_agents_path = os . path . join (
settings . DEPLOY_ROOT , " zerver/tests/fixtures/user_agents_unique "
)
2020-10-24 09:33:54 +02:00
with open ( user_agents_path ) as f :
for line in f :
line = line . strip ( )
match = re . match ( ' ^(?P<count>[0-9]+) " (?P<user_agent>.*) " $ ' , line )
assert match is not None
groupdict = match . groupdict ( )
count = groupdict [ " count " ]
user_agent = groupdict [ " user_agent " ]
ret = parse_user_agent ( user_agent )
user_agents_parsed [ ret [ " name " ] ] + = int ( count )
2017-09-16 08:24:17 +02:00
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
# Check hashable argument.
result = f ( 1 )
2022-06-26 20:21:11 +02:00
info = f . cache_info ( )
2018-01-12 08:57:10 +01:00
# First one should be a miss.
2022-06-26 20:21:11 +02:00
self . assertEqual ( info . hits , 0 )
self . assertEqual ( info . misses , 1 )
self . assertEqual ( info . currsize , 1 )
2018-01-12 08:57:10 +01:00
self . assertEqual ( result , 1 )
result = f ( 1 )
2022-06-26 20:21:11 +02:00
info = f . cache_info ( )
2018-01-12 08:57:10 +01:00
# Second one should be a hit.
2022-06-26 20:21:11 +02:00
self . assertEqual ( info . hits , 1 )
self . assertEqual ( info . misses , 1 )
self . assertEqual ( info . currsize , 1 )
2018-01-12 08:57:10 +01:00
self . assertEqual ( result , 1 )
# Check unhashable argument.
2019-04-18 04:35:14 +02:00
result = f ( { 1 : 2 } )
2022-06-26 20:21:11 +02:00
info = f . cache_info ( )
2018-01-12 08:57:10 +01:00
# Cache should not be used.
2022-06-26 20:21:11 +02:00
self . assertEqual ( info . hits , 1 )
self . assertEqual ( info . misses , 1 )
self . assertEqual ( info . currsize , 1 )
2019-04-18 04:35:14 +02:00
self . assertEqual ( result , { 1 : 2 } )
# Clear cache.
2022-06-26 20:21:11 +02:00
f . cache_clear ( )
info = f . cache_info ( )
self . assertEqual ( info . hits , 0 )
self . assertEqual ( info . misses , 0 )
self . assertEqual ( info . currsize , 0 )
2019-04-18 04:35:14 +02:00
def test_cache_hit_dict_args ( self ) - > None :
@ignore_unhashable_lru_cache ( )
@items_tuple_to_dict
def g ( arg : Any ) - > Any :
return arg
2022-06-26 20:21:11 +02:00
# Not used as a decorator on the definition to allow calling
# cache_info and cache_clear
2019-04-18 04:35:14 +02:00
f = dict_to_items_tuple ( g )
# Check hashable argument.
result = f ( 1 )
2022-06-26 20:21:11 +02:00
info = g . cache_info ( )
2019-04-18 04:35:14 +02:00
# First one should be a miss.
2022-06-26 20:21:11 +02:00
self . assertEqual ( info . hits , 0 )
self . assertEqual ( info . misses , 1 )
self . assertEqual ( info . currsize , 1 )
2019-04-18 04:35:14 +02:00
self . assertEqual ( result , 1 )
result = f ( 1 )
2022-06-26 20:21:11 +02:00
info = g . cache_info ( )
2019-04-18 04:35:14 +02:00
# Second one should be a hit.
2022-06-26 20:21:11 +02:00
self . assertEqual ( info . hits , 1 )
self . assertEqual ( info . misses , 1 )
self . assertEqual ( info . currsize , 1 )
2019-04-18 04:35:14 +02:00
self . assertEqual ( result , 1 )
# Check dict argument.
result = f ( { 1 : 2 } )
2022-06-26 20:21:11 +02:00
info = g . cache_info ( )
2019-04-18 04:35:14 +02:00
# First one is a miss
2022-06-26 20:21:11 +02:00
self . assertEqual ( info . hits , 1 )
self . assertEqual ( info . misses , 2 )
self . assertEqual ( info . currsize , 2 )
2019-04-18 04:35:14 +02:00
self . assertEqual ( result , { 1 : 2 } )
result = f ( { 1 : 2 } )
2022-06-26 20:21:11 +02:00
info = g . cache_info ( )
2019-04-18 04:35:14 +02:00
# Second one should be a hit.
2022-06-26 20:21:11 +02:00
self . assertEqual ( info . hits , 2 )
self . assertEqual ( info . misses , 2 )
self . assertEqual ( info . currsize , 2 )
2019-04-18 04:35:14 +02:00
self . assertEqual ( result , { 1 : 2 } )
2018-01-12 08:57:10 +01:00
# Clear cache.
2022-06-26 20:21:11 +02:00
g . cache_clear ( )
info = g . cache_info ( )
self . assertEqual ( info . hits , 0 )
self . assertEqual ( info . misses , 0 )
self . assertEqual ( info . currsize , 0 )
2021-09-26 18:54:45 +02:00
class TestRequestNotes ( ZulipTestCase ) :
def test_request_notes_realm ( self ) - > None :
"""
This test verifies that . realm gets set correctly on the request notes
depending on the subdomain .
"""
def mock_home ( expected_realm : Optional [ Realm ] ) - > Callable [ [ HttpRequest ] , HttpResponse ] :
def inner ( request : HttpRequest ) - > HttpResponse :
self . assertEqual ( RequestNotes . get_notes ( request ) . realm , expected_realm )
return HttpResponse ( )
return inner
zulip_realm = get_realm ( " zulip " )
2021-10-03 14:10:56 +02:00
# We don't need to test if user is logged in here, so we patch zulip_login_required.
with mock . patch ( " zerver.views.home.zulip_login_required " , lambda f : mock_home ( zulip_realm ) ) :
2021-09-26 18:54:45 +02:00
result = self . client_get ( " / " , subdomain = " zulip " )
self . assertEqual ( result . status_code , 200 )
# When a request is made to the root subdomain and there is no realm on it,
# no realm can be set on the request notes.
2021-10-03 14:10:56 +02:00
with mock . patch ( " zerver.views.home.zulip_login_required " , lambda f : mock_home ( None ) ) :
2021-09-26 18:54:45 +02:00
result = self . client_get ( " / " , subdomain = " " )
2022-04-19 11:47:26 +02:00
self . assertEqual ( result . status_code , 404 )
2021-09-26 18:54:45 +02:00
root_subdomain_realm = do_create_realm ( " " , " Root Domain " )
# Now test that that realm does get set, if it exists, for requests
# to the root subdomain.
2021-10-03 14:10:56 +02:00
with mock . patch (
" zerver.views.home.zulip_login_required " , lambda f : mock_home ( root_subdomain_realm )
) :
2021-09-26 18:54:45 +02:00
result = self . client_get ( " / " , subdomain = " " )
self . assertEqual ( result . status_code , 200 )
# Only the root subdomain allows requests to it without having a realm.
# Requests to non-root subdomains get stopped by the middleware and
# an error page is returned before the request hits the view.
2021-10-03 14:10:56 +02:00
with mock . patch ( " zerver.views.home.zulip_login_required " ) as mock_home_real :
2021-09-26 18:54:45 +02:00
result = self . client_get ( " / " , subdomain = " invalid " )
self . assertEqual ( result . status_code , 404 )
self . assert_in_response (
" There is no Zulip organization hosted at this subdomain. " , result
)
mock_home_real . assert_not_called ( )
2023-02-14 22:45:00 +01:00
class ClientTestCase ( ZulipTestCase ) :
def test_process_client ( self ) - > None :
def request_user_agent ( user_agent : str ) - > Tuple [ Client , str ] :
request = HttpRequest ( )
request . META [ " HTTP_USER_AGENT " ] = user_agent
LogRequests ( lambda request : HttpResponse ( ) ) . process_request ( request )
process_client ( request )
notes = RequestNotes . get_notes ( request )
assert notes . client is not None
assert notes . client_name is not None
return notes . client , notes . client_name
self . assertEqual ( Client . objects . filter ( name = " ZulipThingy " ) . count ( ) , 0 )
with queries_captured ( ) as queries :
client , client_name = request_user_agent ( " ZulipThingy/1.0.0 " )
self . assertEqual ( client . name , " ZulipThingy " )
self . assertEqual ( client_name , " ZulipThingy " )
self . assertEqual ( Client . objects . filter ( name = " ZulipThingy " ) . count ( ) , 1 )
self . assert_length ( queries , 2 )
# Ensure our in-memory cache prevents another database hit
with queries_captured ( ) as queries :
client , client_name = request_user_agent (
" ZulipThingy/1.0.0 " ,
)
self . assertEqual ( client . name , " ZulipThingy " )
self . assertEqual ( client_name , " ZulipThingy " )
self . assert_length ( queries , 0 )
# This operates on the extracted value, so different ZulipThingy versions don't cause another DB query
with queries_captured ( ) as queries :
client , client_name = request_user_agent (
" ZulipThingy/2.0.0 " ,
)
self . assertEqual ( client . name , " ZulipThingy " )
self . assertEqual ( client_name , " ZulipThingy " )
self . assert_length ( queries , 0 )
# If we clear the memory cache we see a database query but get
# the same client-id back.
clear_client_cache ( )
with queries_captured ( ) as queries :
fresh_client , client_name = request_user_agent (
" ZulipThingy/2.0.0 " ,
)
self . assertEqual ( fresh_client . name , " ZulipThingy " )
self . assertEqual ( client , fresh_client )
self . assert_length ( queries , 1 )
# Ensure that long parsed user-agents (longer than 30 characters) work
with queries_captured ( ) as queries :
client , client_name = request_user_agent (
" very-long-name-goes-here-and-somewhere-else (client@example.com) "
)
self . assertEqual ( client . name , " very-long-name-goes-here-and-s " )
# client_name has the full name still, though
self . assertEqual ( client_name , " very-long-name-goes-here-and-somewhere-else " )
self . assert_length ( queries , 2 )
# Longer than that uses the same in-memory cache key, so no database queries
with queries_captured ( ) as queries :
client , client_name = request_user_agent (
" very-long-name-goes-here-and-still-works (client@example.com) "
)
self . assertIsNotNone ( client )
self . assertEqual ( client . name , " very-long-name-goes-here-and-s " )
# client_name has the full name still, though
self . assertEqual ( client_name , " very-long-name-goes-here-and-still-works " )
self . assert_length ( queries , 0 )
2022-08-25 18:41:46 +02:00
class TestIgnoredParametersUnsupported ( ZulipTestCase ) :
def test_ignored_parameters_json_success ( self ) - > None :
@has_request_variables
def test_view (
request : HttpRequest ,
name : Optional [ str ] = REQ ( default = None ) ,
age : Optional [ int ] = 0 ,
) - > HttpResponse :
return json_success ( request )
# ignored parameter (not processed through REQ)
request = HostRequestMock ( )
request . POST [ " age " ] = " 30 "
result = test_view ( request )
self . assert_json_success ( result , ignored_parameters = [ " age " ] )
# valid parameter, returns no ignored parameters
request = HostRequestMock ( )
request . POST [ " name " ] = " Hamlet "
result = test_view ( request )
self . assert_json_success ( result )
# both valid and ignored parameters
request = HostRequestMock ( )
request . POST [ " name " ] = " Hamlet "
request . POST [ " age " ] = " 30 "
request . POST [ " location " ] = " Denmark "
request . POST [ " dies " ] = " True "
result = test_view ( request )
ignored_parameters = [ " age " , " dies " , " location " ]
json_result = self . assert_json_success ( result , ignored_parameters = ignored_parameters )
# check that results are sorted
self . assertEqual ( json_result [ " ignored_parameters_unsupported " ] , ignored_parameters )
# Because `has_request_variables` can be called multiple times on a request,
# here we test that parameters processed in separate, nested function calls
# are not returned in the `ignored parameters_unsupported` array.
def test_nested_has_request_variables ( self ) - > None :
@has_request_variables
def not_view_function_A (
request : HttpRequest , dies : bool = REQ ( json_validator = check_bool )
) - > None :
return
@has_request_variables
def not_view_function_B (
request : HttpRequest , married : bool = REQ ( json_validator = check_bool )
) - > None :
return
@has_request_variables
def view_B ( request : HttpRequest , name : str = REQ ( ) ) - > MutableJsonResponse :
return json_success ( request )
@has_request_variables
def view_A (
request : HttpRequest , age : int = REQ ( json_validator = check_int )
) - > MutableJsonResponse :
not_view_function_A ( request )
response = view_B ( request )
not_view_function_B ( request )
return response
# valid parameters, returns no ignored parameters
post_data = { " name " : " Hamlet " , " age " : " 30 " , " dies " : " true " , " married " : " false " }
request = HostRequestMock ( post_data )
result = view_A ( request )
result_iter = list ( iter ( result ) )
self . assertEqual ( result_iter , [ b ' { " result " : " success " , " msg " : " " } \n ' ] )
self . assert_json_success ( result )
# ignored parameter
post_data = {
" name " : " Hamlet " ,
" age " : " 30 " ,
" dies " : " true " ,
" married " : " false " ,
" author " : " William Shakespeare " ,
}
request = HostRequestMock ( post_data )
result = view_A ( request )
result_iter = list ( iter ( result ) )
self . assertEqual (
result_iter ,
[ b ' { " result " : " success " , " msg " : " " , " ignored_parameters_unsupported " :[ " author " ]} \n ' ] ,
)
self . assert_json_success ( result , ignored_parameters = [ " author " ] )