mirror of
https://github.com/zulip/zulip.git
synced 2025-11-06 23:13:25 +00:00
test_classes: Use OpenAPI request validator for validating requests.
Expand the `validate_api_response_openapi` function to also validate requests to ensure better testing.
This commit is contained in:
@@ -64,7 +64,7 @@ from zerver.models import (
|
|||||||
get_user,
|
get_user,
|
||||||
get_user_by_delivery_email,
|
get_user_by_delivery_email,
|
||||||
)
|
)
|
||||||
from zerver.openapi.openapi import validate_against_openapi_schema
|
from zerver.openapi.openapi import validate_against_openapi_schema, validate_request
|
||||||
from zerver.tornado.event_queue import clear_client_event_queues_for_testing
|
from zerver.tornado.event_queue import clear_client_event_queues_for_testing
|
||||||
from zilencer.models import get_remote_server_by_uuid
|
from zilencer.models import get_remote_server_by_uuid
|
||||||
|
|
||||||
@@ -151,7 +151,21 @@ class ZulipTestCase(TestCase):
|
|||||||
elif 'HTTP_USER_AGENT' not in kwargs:
|
elif 'HTTP_USER_AGENT' not in kwargs:
|
||||||
kwargs['HTTP_USER_AGENT'] = default_user_agent
|
kwargs['HTTP_USER_AGENT'] = default_user_agent
|
||||||
|
|
||||||
def validate_api_response_openapi(self, url: str, method: str, result: HttpResponse) -> None:
|
def extract_api_suffix_url(self, url: str) -> Tuple[str, Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Function that extracts the url after `/api/v1` or `/json` and also
|
||||||
|
returns the query data in the url, if there is any.
|
||||||
|
"""
|
||||||
|
url_split = url.split('?')
|
||||||
|
data: Dict[str, Any] = {}
|
||||||
|
if len(url_split) == 2:
|
||||||
|
data = urllib.parse.parse_qs(url_split[1])
|
||||||
|
url = url_split[0]
|
||||||
|
url = url.replace("/json/", "/").replace("/api/v1/", "/")
|
||||||
|
return (url, data)
|
||||||
|
|
||||||
|
def validate_api_response_openapi(self, url: str, method: str, result: HttpResponse, data: Dict[str, Any],
|
||||||
|
http_headers: Dict[str, Any], intentionally_undocumented: Optional[bool] = False) -> None:
|
||||||
"""
|
"""
|
||||||
Validates all API responses received by this test against Zulip's API documentation,
|
Validates all API responses received by this test against Zulip's API documentation,
|
||||||
declared in zerver/openapi/zulip.yaml. This powerful test lets us use Zulip's
|
declared in zerver/openapi/zulip.yaml. This powerful test lets us use Zulip's
|
||||||
@@ -160,18 +174,26 @@ class ZulipTestCase(TestCase):
|
|||||||
"""
|
"""
|
||||||
if not (url.startswith("/json") or url.startswith("/api/v1")):
|
if not (url.startswith("/json") or url.startswith("/api/v1")):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
content = ujson.loads(result.content)
|
content = ujson.loads(result.content)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return
|
return
|
||||||
url = re.sub(r"\?.*", "", url)
|
json_url = False
|
||||||
validate_against_openapi_schema(content,
|
if url.startswith('/json'):
|
||||||
url.replace("/json/", "/").replace("/api/v1/", "/"),
|
json_url = True
|
||||||
method, str(result.status_code))
|
url, query_data = self.extract_api_suffix_url(url)
|
||||||
|
if len(query_data) != 0:
|
||||||
|
# In some cases the query parameters are defined in the url itself. In such cases
|
||||||
|
# The `data` argument of our function is not used. Hence get `data` arguement
|
||||||
|
# from url.
|
||||||
|
data = query_data
|
||||||
|
response_validated = validate_against_openapi_schema(content, url, method, str(result.status_code))
|
||||||
|
if response_validated:
|
||||||
|
validate_request(url, method, data, http_headers, json_url, str(result.status_code),
|
||||||
|
intentionally_undocumented=intentionally_undocumented)
|
||||||
|
|
||||||
@instrument_url
|
@instrument_url
|
||||||
def client_patch(self, url: str, info: Dict[str, Any]={}, **kwargs: Any) -> HttpResponse:
|
def client_patch(self, url: str, info: Dict[str, Any]={}, intentionally_undocumented: bool=False, **kwargs: Any) -> HttpResponse:
|
||||||
"""
|
"""
|
||||||
We need to urlencode, since Django's function won't do it for us.
|
We need to urlencode, since Django's function won't do it for us.
|
||||||
"""
|
"""
|
||||||
@@ -179,7 +201,7 @@ class ZulipTestCase(TestCase):
|
|||||||
django_client = self.client # see WRAPPER_COMMENT
|
django_client = self.client # see WRAPPER_COMMENT
|
||||||
self.set_http_headers(kwargs)
|
self.set_http_headers(kwargs)
|
||||||
result = django_client.patch(url, encoded, **kwargs)
|
result = django_client.patch(url, encoded, **kwargs)
|
||||||
self.validate_api_response_openapi(url, "patch", result)
|
self.validate_api_response_openapi(url, "patch", result, info, kwargs, intentionally_undocumented=intentionally_undocumented)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@instrument_url
|
@instrument_url
|
||||||
@@ -200,7 +222,7 @@ class ZulipTestCase(TestCase):
|
|||||||
encoded,
|
encoded,
|
||||||
content_type=MULTIPART_CONTENT,
|
content_type=MULTIPART_CONTENT,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
self.validate_api_response_openapi(url, "patch", result)
|
self.validate_api_response_openapi(url, "patch", result, info, kwargs)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@instrument_url
|
@instrument_url
|
||||||
@@ -216,7 +238,7 @@ class ZulipTestCase(TestCase):
|
|||||||
django_client = self.client # see WRAPPER_COMMENT
|
django_client = self.client # see WRAPPER_COMMENT
|
||||||
self.set_http_headers(kwargs)
|
self.set_http_headers(kwargs)
|
||||||
result = django_client.delete(url, encoded, **kwargs)
|
result = django_client.delete(url, encoded, **kwargs)
|
||||||
self.validate_api_response_openapi(url, "delete", result)
|
self.validate_api_response_openapi(url, "delete", result, info, kwargs)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@instrument_url
|
@instrument_url
|
||||||
@@ -234,11 +256,11 @@ class ZulipTestCase(TestCase):
|
|||||||
return django_client.head(url, encoded, **kwargs)
|
return django_client.head(url, encoded, **kwargs)
|
||||||
|
|
||||||
@instrument_url
|
@instrument_url
|
||||||
def client_post(self, url: str, info: Dict[str, Any]={}, **kwargs: Any) -> HttpResponse:
|
def client_post(self, url: str, info: Dict[str, Any]={}, intentionally_undocumented: bool=False, **kwargs: Any) -> HttpResponse:
|
||||||
django_client = self.client # see WRAPPER_COMMENT
|
django_client = self.client # see WRAPPER_COMMENT
|
||||||
self.set_http_headers(kwargs)
|
self.set_http_headers(kwargs)
|
||||||
result = django_client.post(url, info, **kwargs)
|
result = django_client.post(url, info, **kwargs)
|
||||||
self.validate_api_response_openapi(url, "post", result)
|
self.validate_api_response_openapi(url, "post", result, info, kwargs, intentionally_undocumented=intentionally_undocumented)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@instrument_url
|
@instrument_url
|
||||||
@@ -256,11 +278,11 @@ class ZulipTestCase(TestCase):
|
|||||||
return match.func(req)
|
return match.func(req)
|
||||||
|
|
||||||
@instrument_url
|
@instrument_url
|
||||||
def client_get(self, url: str, info: Dict[str, Any]={}, **kwargs: Any) -> HttpResponse:
|
def client_get(self, url: str, info: Dict[str, Any]={}, intentionally_undocumented: bool=False, **kwargs: Any) -> HttpResponse:
|
||||||
django_client = self.client # see WRAPPER_COMMENT
|
django_client = self.client # see WRAPPER_COMMENT
|
||||||
self.set_http_headers(kwargs)
|
self.set_http_headers(kwargs)
|
||||||
result = django_client.get(url, info, **kwargs)
|
result = django_client.get(url, info, **kwargs)
|
||||||
self.validate_api_response_openapi(url, "get", result)
|
self.validate_api_response_openapi(url, "get", result, info, kwargs, intentionally_undocumented=intentionally_undocumented)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
example_user_map = dict(
|
example_user_map = dict(
|
||||||
@@ -550,9 +572,9 @@ class ZulipTestCase(TestCase):
|
|||||||
kwargs['HTTP_AUTHORIZATION'] = self.encode_user(user)
|
kwargs['HTTP_AUTHORIZATION'] = self.encode_user(user)
|
||||||
return self.client_get(*args, **kwargs)
|
return self.client_get(*args, **kwargs)
|
||||||
|
|
||||||
def api_post(self, user: UserProfile, *args: Any, **kwargs: Any) -> HttpResponse:
|
def api_post(self, user: UserProfile, *args: Any, intentionally_undocumented: bool=False, **kwargs: Any) -> HttpResponse:
|
||||||
kwargs['HTTP_AUTHORIZATION'] = self.encode_user(user)
|
kwargs['HTTP_AUTHORIZATION'] = self.encode_user(user)
|
||||||
return self.client_post(*args, **kwargs)
|
return self.client_post(*args, intentionally_undocumented=intentionally_undocumented, **kwargs)
|
||||||
|
|
||||||
def api_patch(self, user: UserProfile, *args: Any, **kwargs: Any) -> HttpResponse:
|
def api_patch(self, user: UserProfile, *args: Any, **kwargs: Any) -> HttpResponse:
|
||||||
kwargs['HTTP_AUTHORIZATION'] = self.encode_user(user)
|
kwargs['HTTP_AUTHORIZATION'] = self.encode_user(user)
|
||||||
|
|||||||
Reference in New Issue
Block a user