557 lines
19 KiB
Python
557 lines
19 KiB
Python
|
import unittest2
|
||
|
|
||
|
|
||
|
class Test__httplib2_debug_level(unittest2.TestCase):
|
||
|
|
||
|
def _getTargetClass(self):
|
||
|
from gcloud.streaming.http_wrapper import _httplib2_debug_level
|
||
|
return _httplib2_debug_level
|
||
|
|
||
|
def _makeOne(self, *args, **kw):
|
||
|
return self._getTargetClass()(*args, **kw)
|
||
|
|
||
|
def test_wo_loggable_body_wo_http(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
|
||
|
request = _Request()
|
||
|
LEVEL = 1
|
||
|
_httplib2 = _Dummy(debuglevel=0)
|
||
|
with _Monkey(MUT, httplib2=_httplib2):
|
||
|
with self._makeOne(request, LEVEL):
|
||
|
self.assertEqual(_httplib2.debuglevel, 0)
|
||
|
|
||
|
def test_w_loggable_body_wo_http(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
|
||
|
request = _Request(loggable_body=object())
|
||
|
LEVEL = 1
|
||
|
_httplib2 = _Dummy(debuglevel=0)
|
||
|
with _Monkey(MUT, httplib2=_httplib2):
|
||
|
with self._makeOne(request, LEVEL):
|
||
|
self.assertEqual(_httplib2.debuglevel, LEVEL)
|
||
|
self.assertEqual(_httplib2.debuglevel, 0)
|
||
|
|
||
|
def test_w_loggable_body_w_http(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
|
||
|
class _Connection(object):
|
||
|
debuglevel = 0
|
||
|
|
||
|
def set_debuglevel(self, value):
|
||
|
self.debuglevel = value
|
||
|
|
||
|
request = _Request(loggable_body=object())
|
||
|
LEVEL = 1
|
||
|
_httplib2 = _Dummy(debuglevel=0)
|
||
|
update_me = _Connection()
|
||
|
skip_me = _Connection()
|
||
|
connections = {'update:me': update_me, 'skip_me': skip_me}
|
||
|
_http = _Dummy(connections=connections)
|
||
|
with _Monkey(MUT, httplib2=_httplib2):
|
||
|
with self._makeOne(request, LEVEL, _http):
|
||
|
self.assertEqual(_httplib2.debuglevel, LEVEL)
|
||
|
self.assertEqual(update_me.debuglevel, LEVEL)
|
||
|
self.assertEqual(skip_me.debuglevel, 0)
|
||
|
self.assertEqual(_httplib2.debuglevel, 0)
|
||
|
self.assertEqual(update_me.debuglevel, 0)
|
||
|
self.assertEqual(skip_me.debuglevel, 0)
|
||
|
|
||
|
|
||
|
class Test_Request(unittest2.TestCase):
|
||
|
|
||
|
def _getTargetClass(self):
|
||
|
from gcloud.streaming.http_wrapper import Request
|
||
|
return Request
|
||
|
|
||
|
def _makeOne(self, *args, **kw):
|
||
|
return self._getTargetClass()(*args, **kw)
|
||
|
|
||
|
def test_ctor_defaults(self):
|
||
|
request = self._makeOne()
|
||
|
self.assertEqual(request.url, '')
|
||
|
self.assertEqual(request.http_method, 'GET')
|
||
|
self.assertEqual(request.headers, {'content-length': '0'})
|
||
|
self.assertEqual(request.body, '')
|
||
|
self.assertEqual(request.loggable_body, None)
|
||
|
|
||
|
def test_loggable_body_setter_w_body_None(self):
|
||
|
from gcloud.streaming.exceptions import RequestError
|
||
|
request = self._makeOne(body=None)
|
||
|
with self.assertRaises(RequestError):
|
||
|
request.loggable_body = 'abc'
|
||
|
|
||
|
def test_body_setter_w_None(self):
|
||
|
request = self._makeOne()
|
||
|
request.loggable_body = 'abc'
|
||
|
request.body = None
|
||
|
self.assertEqual(request.headers, {})
|
||
|
self.assertEqual(request.body, None)
|
||
|
self.assertEqual(request.loggable_body, 'abc')
|
||
|
|
||
|
def test_body_setter_w_non_string(self):
|
||
|
request = self._makeOne()
|
||
|
request.loggable_body = 'abc'
|
||
|
request.body = body = _Dummy(length=123)
|
||
|
self.assertEqual(request.headers, {'content-length': '123'})
|
||
|
self.assertTrue(request.body is body)
|
||
|
self.assertEqual(request.loggable_body, '<media body>')
|
||
|
|
||
|
|
||
|
class Test_Response(unittest2.TestCase):
|
||
|
|
||
|
def _getTargetClass(self):
|
||
|
from gcloud.streaming.http_wrapper import Response
|
||
|
return Response
|
||
|
|
||
|
def _makeOne(self, *args, **kw):
|
||
|
return self._getTargetClass()(*args, **kw)
|
||
|
|
||
|
def test_ctor(self):
|
||
|
CONTENT = 'CONTENT'
|
||
|
URL = 'http://example.com/api'
|
||
|
info = {'status': '200'}
|
||
|
response = self._makeOne(info, CONTENT, URL)
|
||
|
self.assertEqual(len(response), len(CONTENT))
|
||
|
self.assertEqual(response.status_code, 200)
|
||
|
self.assertEqual(response.retry_after, None)
|
||
|
self.assertFalse(response.is_redirect)
|
||
|
|
||
|
def test_length_w_content_encoding_w_content_range(self):
|
||
|
CONTENT = 'CONTENT'
|
||
|
URL = 'http://example.com/api'
|
||
|
RANGE = 'bytes 0-122/5678'
|
||
|
info = {
|
||
|
'status': '200',
|
||
|
'content-length': len(CONTENT),
|
||
|
'content-encoding': 'testing',
|
||
|
'content-range': RANGE,
|
||
|
}
|
||
|
response = self._makeOne(info, CONTENT, URL)
|
||
|
self.assertEqual(len(response), 123)
|
||
|
|
||
|
def test_length_w_content_encoding_wo_content_range(self):
|
||
|
CONTENT = 'CONTENT'
|
||
|
URL = 'http://example.com/api'
|
||
|
info = {
|
||
|
'status': '200',
|
||
|
'content-length': len(CONTENT),
|
||
|
'content-encoding': 'testing',
|
||
|
}
|
||
|
response = self._makeOne(info, CONTENT, URL)
|
||
|
self.assertEqual(len(response), len(CONTENT))
|
||
|
|
||
|
def test_length_w_content_length_w_content_range(self):
|
||
|
CONTENT = 'CONTENT'
|
||
|
URL = 'http://example.com/api'
|
||
|
RANGE = 'bytes 0-12/5678'
|
||
|
info = {
|
||
|
'status': '200',
|
||
|
'content-length': len(CONTENT) * 2,
|
||
|
'content-range': RANGE,
|
||
|
}
|
||
|
response = self._makeOne(info, CONTENT, URL)
|
||
|
self.assertEqual(len(response), len(CONTENT) * 2)
|
||
|
|
||
|
def test_length_wo_content_length_w_content_range(self):
|
||
|
CONTENT = 'CONTENT'
|
||
|
URL = 'http://example.com/api'
|
||
|
RANGE = 'bytes 0-122/5678'
|
||
|
info = {
|
||
|
'status': '200',
|
||
|
'content-range': RANGE,
|
||
|
}
|
||
|
response = self._makeOne(info, CONTENT, URL)
|
||
|
self.assertEqual(len(response), 123)
|
||
|
|
||
|
def test_retry_after_w_header(self):
|
||
|
CONTENT = 'CONTENT'
|
||
|
URL = 'http://example.com/api'
|
||
|
info = {
|
||
|
'status': '200',
|
||
|
'retry-after': '123',
|
||
|
}
|
||
|
response = self._makeOne(info, CONTENT, URL)
|
||
|
self.assertEqual(response.retry_after, 123)
|
||
|
|
||
|
def test_is_redirect_w_code_wo_location(self):
|
||
|
CONTENT = 'CONTENT'
|
||
|
URL = 'http://example.com/api'
|
||
|
info = {
|
||
|
'status': '301',
|
||
|
}
|
||
|
response = self._makeOne(info, CONTENT, URL)
|
||
|
self.assertFalse(response.is_redirect)
|
||
|
|
||
|
def test_is_redirect_w_code_w_location(self):
|
||
|
CONTENT = 'CONTENT'
|
||
|
URL = 'http://example.com/api'
|
||
|
info = {
|
||
|
'status': '301',
|
||
|
'location': 'http://example.com/other',
|
||
|
}
|
||
|
response = self._makeOne(info, CONTENT, URL)
|
||
|
self.assertTrue(response.is_redirect)
|
||
|
|
||
|
|
||
|
class Test__check_response(unittest2.TestCase):
|
||
|
|
||
|
def _callFUT(self, *args, **kw):
|
||
|
from gcloud.streaming.http_wrapper import _check_response
|
||
|
return _check_response(*args, **kw)
|
||
|
|
||
|
def test_w_none(self):
|
||
|
from gcloud.streaming.exceptions import RequestError
|
||
|
with self.assertRaises(RequestError):
|
||
|
self._callFUT(None)
|
||
|
|
||
|
def test_w_TOO_MANY_REQUESTS(self):
|
||
|
from gcloud.streaming.exceptions import BadStatusCodeError
|
||
|
from gcloud.streaming.http_wrapper import TOO_MANY_REQUESTS
|
||
|
|
||
|
with self.assertRaises(BadStatusCodeError):
|
||
|
self._callFUT(_Response(TOO_MANY_REQUESTS))
|
||
|
|
||
|
def test_w_50x(self):
|
||
|
from gcloud.streaming.exceptions import BadStatusCodeError
|
||
|
|
||
|
with self.assertRaises(BadStatusCodeError):
|
||
|
self._callFUT(_Response(500))
|
||
|
|
||
|
with self.assertRaises(BadStatusCodeError):
|
||
|
self._callFUT(_Response(503))
|
||
|
|
||
|
def test_w_retry_after(self):
|
||
|
from gcloud.streaming.exceptions import RetryAfterError
|
||
|
|
||
|
with self.assertRaises(RetryAfterError):
|
||
|
self._callFUT(_Response(200, 20))
|
||
|
|
||
|
def test_pass(self):
|
||
|
self._callFUT(_Response(200))
|
||
|
|
||
|
|
||
|
class Test__reset_http_connections(unittest2.TestCase):
|
||
|
|
||
|
def _callFUT(self, *args, **kw):
|
||
|
from gcloud.streaming.http_wrapper import _reset_http_connections
|
||
|
return _reset_http_connections(*args, **kw)
|
||
|
|
||
|
def test_wo_connections(self):
|
||
|
http = object()
|
||
|
self._callFUT(http)
|
||
|
|
||
|
def test_w_connections(self):
|
||
|
connections = {'delete:me': object(), 'skip_me': object()}
|
||
|
http = _Dummy(connections=connections)
|
||
|
self._callFUT(http)
|
||
|
self.assertFalse('delete:me' in connections)
|
||
|
self.assertTrue('skip_me' in connections)
|
||
|
|
||
|
|
||
|
class Test___make_api_request_no_retry(unittest2.TestCase):
|
||
|
|
||
|
def _callFUT(self, *args, **kw):
|
||
|
from gcloud.streaming.http_wrapper import _make_api_request_no_retry
|
||
|
return _make_api_request_no_retry(*args, **kw)
|
||
|
|
||
|
def _verify_requested(self, http, request,
|
||
|
redirections=5, connection_type=None):
|
||
|
self.assertEqual(len(http._requested), 1)
|
||
|
url, kw = http._requested[0]
|
||
|
self.assertEqual(url, request.url)
|
||
|
self.assertEqual(kw['method'], request.http_method)
|
||
|
self.assertEqual(kw['body'], request.body)
|
||
|
self.assertEqual(kw['headers'], request.headers)
|
||
|
self.assertEqual(kw['redirections'], redirections)
|
||
|
self.assertEqual(kw['connection_type'], connection_type)
|
||
|
|
||
|
def test_defaults_wo_connections(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
INFO = {'status': '200'}
|
||
|
CONTENT = 'CONTENT'
|
||
|
_http = _Http((INFO, CONTENT))
|
||
|
_httplib2 = _Dummy(debuglevel=1)
|
||
|
_request = _Request()
|
||
|
_checked = []
|
||
|
with _Monkey(MUT, httplib2=_httplib2):
|
||
|
response = self._callFUT(_http, _request,
|
||
|
check_response_func=_checked.append)
|
||
|
self.assertTrue(isinstance(response, MUT.Response))
|
||
|
self.assertEqual(response.info, INFO)
|
||
|
self.assertEqual(response.content, CONTENT)
|
||
|
self.assertEqual(response.request_url, _request.url)
|
||
|
self.assertEqual(_checked, [response])
|
||
|
self._verify_requested(_http, _request)
|
||
|
|
||
|
def test_w_explicit_redirections(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
INFO = {'status': '200'}
|
||
|
CONTENT = 'CONTENT'
|
||
|
_http = _Http((INFO, CONTENT))
|
||
|
_httplib2 = _Dummy(debuglevel=1)
|
||
|
_request = _Request()
|
||
|
_checked = []
|
||
|
with _Monkey(MUT, httplib2=_httplib2):
|
||
|
response = self._callFUT(_http, _request,
|
||
|
redirections=10,
|
||
|
check_response_func=_checked.append)
|
||
|
self.assertTrue(isinstance(response, MUT.Response))
|
||
|
self.assertEqual(response.info, INFO)
|
||
|
self.assertEqual(response.content, CONTENT)
|
||
|
self.assertEqual(response.request_url, _request.url)
|
||
|
self.assertEqual(_checked, [response])
|
||
|
self._verify_requested(_http, _request, redirections=10)
|
||
|
|
||
|
def test_w_http_connections_miss(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
INFO = {'status': '200'}
|
||
|
CONTENT = 'CONTENT'
|
||
|
CONN_TYPE = object()
|
||
|
_http = _Http((INFO, CONTENT))
|
||
|
_http.connections = {'https': CONN_TYPE}
|
||
|
_httplib2 = _Dummy(debuglevel=1)
|
||
|
_request = _Request()
|
||
|
_checked = []
|
||
|
with _Monkey(MUT, httplib2=_httplib2):
|
||
|
response = self._callFUT(_http, _request,
|
||
|
check_response_func=_checked.append)
|
||
|
self.assertTrue(isinstance(response, MUT.Response))
|
||
|
self.assertEqual(response.info, INFO)
|
||
|
self.assertEqual(response.content, CONTENT)
|
||
|
self.assertEqual(response.request_url, _request.url)
|
||
|
self.assertEqual(_checked, [response])
|
||
|
self._verify_requested(_http, _request)
|
||
|
|
||
|
def test_w_http_connections_hit(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
INFO = {'status': '200'}
|
||
|
CONTENT = 'CONTENT'
|
||
|
CONN_TYPE = object()
|
||
|
_http = _Http((INFO, CONTENT))
|
||
|
_http.connections = {'http': CONN_TYPE}
|
||
|
_httplib2 = _Dummy(debuglevel=1)
|
||
|
_request = _Request()
|
||
|
_checked = []
|
||
|
with _Monkey(MUT, httplib2=_httplib2):
|
||
|
response = self._callFUT(_http, _request,
|
||
|
check_response_func=_checked.append)
|
||
|
self.assertTrue(isinstance(response, MUT.Response))
|
||
|
self.assertEqual(response.info, INFO)
|
||
|
self.assertEqual(response.content, CONTENT)
|
||
|
self.assertEqual(response.request_url, _request.url)
|
||
|
self.assertEqual(_checked, [response])
|
||
|
self._verify_requested(_http, _request, connection_type=CONN_TYPE)
|
||
|
|
||
|
def test_w_request_returning_None(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
from gcloud.streaming.exceptions import RequestError
|
||
|
INFO = None
|
||
|
CONTENT = None
|
||
|
CONN_TYPE = object()
|
||
|
_http = _Http((INFO, CONTENT))
|
||
|
_http.connections = {'http': CONN_TYPE}
|
||
|
_httplib2 = _Dummy(debuglevel=1)
|
||
|
_request = _Request()
|
||
|
with _Monkey(MUT, httplib2=_httplib2):
|
||
|
with self.assertRaises(RequestError):
|
||
|
self._callFUT(_http, _request)
|
||
|
self._verify_requested(_http, _request, connection_type=CONN_TYPE)
|
||
|
|
||
|
|
||
|
class Test_make_api_request(unittest2.TestCase):
|
||
|
|
||
|
def _callFUT(self, *args, **kw):
|
||
|
from gcloud.streaming.http_wrapper import make_api_request
|
||
|
return make_api_request(*args, **kw)
|
||
|
|
||
|
def test_wo_exception(self):
|
||
|
HTTP, REQUEST, RESPONSE = object(), object(), object()
|
||
|
_created, _checked = [], []
|
||
|
|
||
|
def _wo_exception(*args, **kw):
|
||
|
_created.append((args, kw))
|
||
|
return RESPONSE
|
||
|
|
||
|
response = self._callFUT(HTTP, REQUEST,
|
||
|
wo_retry_func=_wo_exception,
|
||
|
check_response_func=_checked.append)
|
||
|
|
||
|
self.assertTrue(response is RESPONSE)
|
||
|
expected_kw = {
|
||
|
'redirections': 5,
|
||
|
'check_response_func': _checked.append,
|
||
|
}
|
||
|
self.assertEqual(_created, [((HTTP, REQUEST), expected_kw)])
|
||
|
self.assertEqual(_checked, []) # not called by '_wo_exception'
|
||
|
|
||
|
def test_w_exceptions_lt_max_retries(self):
|
||
|
from gcloud.streaming.exceptions import RetryAfterError
|
||
|
HTTP, RESPONSE = object(), object()
|
||
|
REQUEST = _Request()
|
||
|
WAIT = 10,
|
||
|
_created, _checked = [], []
|
||
|
_counter = [None] * 4
|
||
|
|
||
|
def _wo_exception(*args, **kw):
|
||
|
_created.append((args, kw))
|
||
|
if _counter:
|
||
|
_counter.pop()
|
||
|
raise RetryAfterError(RESPONSE, '', REQUEST.url, 0.1)
|
||
|
return RESPONSE
|
||
|
|
||
|
response = self._callFUT(HTTP, REQUEST,
|
||
|
retries=5,
|
||
|
max_retry_wait=WAIT,
|
||
|
wo_retry_func=_wo_exception,
|
||
|
check_response_func=_checked.append)
|
||
|
|
||
|
self.assertTrue(response is RESPONSE)
|
||
|
self.assertEqual(len(_created), 5)
|
||
|
expected_kw = {
|
||
|
'redirections': 5,
|
||
|
'check_response_func': _checked.append,
|
||
|
}
|
||
|
for attempt in _created:
|
||
|
self.assertEqual(attempt, ((HTTP, REQUEST), expected_kw))
|
||
|
self.assertEqual(_checked, []) # not called by '_wo_exception'
|
||
|
|
||
|
def test_w_exceptions_gt_max_retries(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
HTTP = object()
|
||
|
REQUEST = _Request()
|
||
|
WAIT = 10,
|
||
|
_created, _checked = [], []
|
||
|
|
||
|
def _wo_exception(*args, **kw):
|
||
|
_created.append((args, kw))
|
||
|
raise ValueError('Retryable')
|
||
|
|
||
|
with _Monkey(MUT, calculate_wait_for_retry=lambda *ignored: 0.1):
|
||
|
with self.assertRaises(ValueError):
|
||
|
self._callFUT(HTTP, REQUEST,
|
||
|
retries=3,
|
||
|
max_retry_wait=WAIT,
|
||
|
wo_retry_func=_wo_exception,
|
||
|
check_response_func=_checked.append)
|
||
|
|
||
|
self.assertEqual(len(_created), 3)
|
||
|
expected_kw = {
|
||
|
'redirections': 5,
|
||
|
'check_response_func': _checked.append,
|
||
|
}
|
||
|
for attempt in _created:
|
||
|
self.assertEqual(attempt, ((HTTP, REQUEST), expected_kw))
|
||
|
self.assertEqual(_checked, []) # not called by '_wo_exception'
|
||
|
|
||
|
|
||
|
class Test__register_http_factory(unittest2.TestCase):
|
||
|
|
||
|
def _callFUT(self, *args, **kw):
|
||
|
from gcloud.streaming.http_wrapper import _register_http_factory
|
||
|
return _register_http_factory(*args, **kw)
|
||
|
|
||
|
def test_it(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
_factories = []
|
||
|
|
||
|
FACTORY = object()
|
||
|
|
||
|
with _Monkey(MUT, _HTTP_FACTORIES=_factories):
|
||
|
self._callFUT(FACTORY)
|
||
|
self.assertEqual(_factories, [FACTORY])
|
||
|
|
||
|
|
||
|
class Test_get_http(unittest2.TestCase):
|
||
|
|
||
|
def _callFUT(self, *args, **kw):
|
||
|
from gcloud.streaming.http_wrapper import get_http
|
||
|
return get_http(*args, **kw)
|
||
|
|
||
|
def test_wo_registered_factories(self):
|
||
|
from httplib2 import Http
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
_factories = []
|
||
|
|
||
|
with _Monkey(MUT, _HTTP_FACTORIES=_factories):
|
||
|
http = self._callFUT()
|
||
|
|
||
|
self.assertTrue(isinstance(http, Http))
|
||
|
|
||
|
def test_w_registered_factories(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.streaming import http_wrapper as MUT
|
||
|
|
||
|
FOUND = object()
|
||
|
|
||
|
_misses = []
|
||
|
|
||
|
def _miss(**kw):
|
||
|
_misses.append(kw)
|
||
|
return None
|
||
|
|
||
|
_hits = []
|
||
|
|
||
|
def _hit(**kw):
|
||
|
_hits.append(kw)
|
||
|
return FOUND
|
||
|
|
||
|
_factories = [_miss, _hit]
|
||
|
|
||
|
with _Monkey(MUT, _HTTP_FACTORIES=_factories):
|
||
|
http = self._callFUT(foo='bar')
|
||
|
|
||
|
self.assertTrue(http is FOUND)
|
||
|
self.assertEqual(_misses, [{'foo': 'bar'}])
|
||
|
self.assertEqual(_hits, [{'foo': 'bar'}])
|
||
|
|
||
|
|
||
|
class _Dummy(object):
|
||
|
def __init__(self, **kw):
|
||
|
self.__dict__.update(kw)
|
||
|
|
||
|
|
||
|
class _Request(object):
|
||
|
__slots__ = ('url', 'http_method', 'body', 'headers', 'loggable_body',)
|
||
|
URL = 'http://example.com/api'
|
||
|
|
||
|
def __init__(self, url=URL, http_method='GET', body='',
|
||
|
loggable_body=None):
|
||
|
self.url = url
|
||
|
self.http_method = http_method
|
||
|
self.body = body
|
||
|
self.headers = {}
|
||
|
self.loggable_body = loggable_body
|
||
|
|
||
|
|
||
|
class _Response(object):
|
||
|
content = ''
|
||
|
request_url = _Request.URL
|
||
|
|
||
|
def __init__(self, status_code, retry_after=None):
|
||
|
self.info = {'status': status_code}
|
||
|
self.status_code = status_code
|
||
|
self.retry_after = retry_after
|
||
|
|
||
|
|
||
|
class _Http(object):
|
||
|
|
||
|
def __init__(self, *responses):
|
||
|
self._responses = responses
|
||
|
self._requested = []
|
||
|
|
||
|
def request(self, url, **kw):
|
||
|
self._requested.append((url, kw))
|
||
|
response, self._responses = self._responses[0], self._responses[1:]
|
||
|
return response
|