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