Previously, the responses set in bot test fixtures where handled as JSON objects. This works fine for most bot tests, because most of the APIs that bots are calling return a JSON-formatted response object. However, some, like Trello, do return raw data. This hasn't been noticed so far, because the respective Trello test needed internet access. Tests shouldn't need internet access. This commit makes that Trello test use a fixture. To work properly, it also adds a way to make http_mock_config parse the response object as raw data. This can now be done by modifying the "is_raw_response" property in a newly introduced "meta" object that can be used to specify how a fixture should be handled.
104 lines
3.7 KiB
Python
104 lines
3.7 KiB
Python
import json
|
|
import requests
|
|
|
|
from contextlib import contextmanager
|
|
from unittest.mock import patch
|
|
|
|
from typing import Any, Dict, List
|
|
|
|
@contextmanager
|
|
def mock_http_conversation(http_data):
|
|
# type: (Dict[str, Any]) -> Any
|
|
"""
|
|
Use this context manager to mock and verify a bot's HTTP
|
|
requests to the third-party API (and provide the correct
|
|
third-party API response. This allows us to test things
|
|
that would require the Internet without it).
|
|
|
|
http_data should be fixtures data formatted like the data
|
|
in zulip_bots/zulip_bots/bots/giphy/fixtures/test_normal.json
|
|
"""
|
|
def get_response(http_response, http_headers, is_raw_response):
|
|
# type: (Dict[str, Any], Dict[str, Any], bool) -> Any
|
|
"""Creates a fake `requests` Response with a desired HTTP response and
|
|
response headers.
|
|
"""
|
|
mock_result = requests.Response()
|
|
if is_raw_response:
|
|
mock_result._content = http_response.encode() # type: ignore # This modifies a "hidden" attribute.
|
|
else:
|
|
mock_result._content = json.dumps(http_response).encode() # type: ignore # See above.
|
|
mock_result.status_code = http_headers.get('status', 200)
|
|
return mock_result
|
|
|
|
def assert_called_with_fields(mock_result, http_request, fields, meta):
|
|
# type: (Any, Dict[str, Any], List[str], Dict[str, Any]) -> None
|
|
"""Calls `assert_called_with` on a mock object using an HTTP request.
|
|
Uses `fields` to determine which keys to look for in HTTP request and
|
|
to test; if a key is in `fields`, e.g., 'headers', it will be used in
|
|
the assertion.
|
|
"""
|
|
args = {}
|
|
|
|
for field in fields:
|
|
if field in http_request:
|
|
args[field] = http_request[field]
|
|
|
|
mock_result.assert_called_with(http_request['api_url'], **args)
|
|
|
|
try:
|
|
http_request = http_data['request']
|
|
http_response = http_data['response']
|
|
http_headers = http_data['response-headers']
|
|
except KeyError:
|
|
print("ERROR: Failed to find 'request', 'response' or 'response-headers' fields in fixture")
|
|
raise
|
|
|
|
meta = http_data.get('meta', dict())
|
|
is_raw_response = meta.get('is_raw_response', False)
|
|
|
|
http_method = http_request.get('method', 'GET')
|
|
|
|
if http_method == 'GET':
|
|
with patch('requests.get') as mock_get:
|
|
mock_get.return_value = get_response(http_response, http_headers, is_raw_response)
|
|
yield
|
|
assert_called_with_fields(
|
|
mock_get,
|
|
http_request,
|
|
['params', 'headers'],
|
|
meta
|
|
)
|
|
elif http_method == 'PATCH':
|
|
with patch('requests.patch') as mock_patch:
|
|
mock_patch.return_value = get_response(http_response, http_headers, is_raw_response)
|
|
yield
|
|
assert_called_with_fields(
|
|
mock_patch,
|
|
http_request,
|
|
['params', 'headers', 'json', 'data'],
|
|
meta
|
|
)
|
|
else:
|
|
with patch('requests.post') as mock_post:
|
|
mock_post.return_value = get_response(http_response, http_headers, is_raw_response)
|
|
yield
|
|
assert_called_with_fields(
|
|
mock_post,
|
|
http_request,
|
|
['params', 'headers', 'json', 'data'],
|
|
meta
|
|
)
|
|
|
|
@contextmanager
|
|
def mock_request_exception():
|
|
# type: () -> Any
|
|
def assert_mock_called(mock_result):
|
|
# type: (Any) -> None
|
|
assert mock_result.called
|
|
|
|
with patch('requests.get') as mock_get:
|
|
mock_get.return_value = True
|
|
mock_get.side_effect = requests.exceptions.RequestException
|
|
yield
|
|
assert_mock_called(mock_get)
|