You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
8.2 KiB

import httplib2
import mock
import os
import pickle
import pytest
import socket
import sys
import tests
import time
from six.moves import urllib
@pytest.mark.skipif(
sys.version_info <= (3,),
reason=(
"TODO: httplib2._convert_byte_str was defined only in python3 code " "version"
),
)
def test_convert_byte_str():
with tests.assert_raises(TypeError):
httplib2._convert_byte_str(4)
assert httplib2._convert_byte_str(b"Hello") == "Hello"
assert httplib2._convert_byte_str("World") == "World"
def test_reflect():
http = httplib2.Http()
with tests.server_reflect() as uri:
response, content = http.request(uri + "?query", "METHOD")
assert response.status == 200
host = urllib.parse.urlparse(uri).netloc
assert content.startswith(
"""\
METHOD /?query HTTP/1.1\r\n\
Host: {host}\r\n""".format(
host=host
).encode()
), content
def test_pickle_http():
http = httplib2.Http(cache=tests.get_cache_path())
new_http = pickle.loads(pickle.dumps(http))
assert tuple(sorted(new_http.__dict__)) == tuple(sorted(http.__dict__))
assert new_http.credentials.credentials == http.credentials.credentials
assert new_http.certificates.credentials == http.certificates.credentials
assert new_http.cache.cache == http.cache.cache
for key in new_http.__dict__:
if key not in ("cache", "certificates", "credentials"):
assert getattr(new_http, key) == getattr(http, key)
def test_pickle_http_with_connection():
http = httplib2.Http()
http.request("http://random-domain:81/", connection_type=tests.MockHTTPConnection)
new_http = pickle.loads(pickle.dumps(http))
assert tuple(http.connections) == ("http:random-domain:81",)
assert new_http.connections == {}
def test_pickle_custom_request_http():
http = httplib2.Http()
http.request = lambda: None
http.request.dummy_attr = "dummy_value"
new_http = pickle.loads(pickle.dumps(http))
assert getattr(new_http.request, "dummy_attr", None) is None
@pytest.mark.xfail(
sys.version_info >= (3,),
reason=(
"FIXME: for unknown reason global timeout test fails in Python3 "
"with response 200"
),
)
def test_timeout_global():
def handler(request):
time.sleep(0.5)
return tests.http_response_bytes()
try:
socket.setdefaulttimeout(0.1)
except Exception:
pytest.skip("cannot set global socket timeout")
try:
http = httplib2.Http()
http.force_exception_to_status_code = True
with tests.server_request(handler) as uri:
response, content = http.request(uri)
assert response.status == 408
assert response.reason.startswith("Request Timeout")
finally:
socket.setdefaulttimeout(None)
def test_timeout_individual():
def handler(request):
time.sleep(0.5)
return tests.http_response_bytes()
http = httplib2.Http(timeout=0.1)
http.force_exception_to_status_code = True
with tests.server_request(handler) as uri:
response, content = http.request(uri)
assert response.status == 408
assert response.reason.startswith("Request Timeout")
def test_timeout_subsequent():
class Handler(object):
number = 0
@classmethod
def handle(cls, request):
# request.number is always 1 because of
# the new socket connection each time
cls.number += 1
if cls.number % 2 != 0:
time.sleep(0.6)
return tests.http_response_bytes(status=500)
return tests.http_response_bytes(status=200)
http = httplib2.Http(timeout=0.5)
http.force_exception_to_status_code = True
with tests.server_request(Handler.handle, request_count=2) as uri:
response, _ = http.request(uri)
assert response.status == 408
assert response.reason.startswith("Request Timeout")
response, _ = http.request(uri)
assert response.status == 200
def test_timeout_https():
c = httplib2.HTTPSConnectionWithTimeout("localhost", 80, timeout=47)
assert 47 == c.timeout
# @pytest.mark.xfail(
# sys.version_info >= (3,),
# reason='[py3] last request should open new connection, but client does not realize socket was closed by server',
# )
def test_connection_close():
http = httplib2.Http()
g = []
def handler(request):
g.append(request.number)
return tests.http_response_bytes(proto="HTTP/1.1")
with tests.server_request(handler, request_count=3) as uri:
http.request(uri, "GET") # conn1 req1
for c in http.connections.values():
assert c.sock is not None
http.request(uri, "GET", headers={"connection": "close"})
time.sleep(0.7)
http.request(uri, "GET") # conn2 req1
assert g == [1, 2, 1]
def test_get_end2end_headers():
# one end to end header
response = {"content-type": "application/atom+xml", "te": "deflate"}
end2end = httplib2._get_end2end_headers(response)
assert "content-type" in end2end
assert "te" not in end2end
assert "connection" not in end2end
# one end to end header that gets eliminated
response = {
"connection": "content-type",
"content-type": "application/atom+xml",
"te": "deflate",
}
end2end = httplib2._get_end2end_headers(response)
assert "content-type" not in end2end
assert "te" not in end2end
assert "connection" not in end2end
# Degenerate case of no headers
response = {}
end2end = httplib2._get_end2end_headers(response)
assert len(end2end) == 0
# Degenerate case of connection referrring to a header not passed in
response = {"connection": "content-type"}
end2end = httplib2._get_end2end_headers(response)
assert len(end2end) == 0
@pytest.mark.xfail(
os.environ.get("TRAVIS_PYTHON_VERSION") in ("2.7", "pypy"),
reason="FIXME: fail on Travis py27 and pypy, works elsewhere",
)
@pytest.mark.parametrize("scheme", ("http", "https"))
def test_ipv6(scheme):
# Even if IPv6 isn't installed on a machine it should just raise socket.error
uri = "{scheme}://[::1]:1/".format(scheme=scheme)
try:
httplib2.Http(timeout=0.1).request(uri)
except socket.gaierror:
assert False, "should get the address family right for IPv6"
except socket.error:
pass
@pytest.mark.parametrize(
"conn_type",
(httplib2.HTTPConnectionWithTimeout, httplib2.HTTPSConnectionWithTimeout),
)
def test_connection_proxy_info_attribute_error(conn_type):
# HTTPConnectionWithTimeout did not initialize its .proxy_info attribute
# https://github.com/httplib2/httplib2/pull/97
# Thanks to Joseph Ryan https://github.com/germanjoey
conn = conn_type("no-such-hostname.", 80)
# TODO: replace mock with dummy local server
with tests.assert_raises(socket.gaierror):
with mock.patch("socket.socket.connect", side_effect=socket.gaierror):
conn.request("GET", "/")
def test_http_443_forced_https():
http = httplib2.Http()
http.force_exception_to_status_code = True
uri = "http://localhost:443/"
# sorry, using internal structure of Http to check chosen scheme
with mock.patch("httplib2.Http._request") as m:
http.request(uri)
assert len(m.call_args) > 0, "expected Http._request() call"
conn = m.call_args[0][0]
assert isinstance(conn, httplib2.HTTPConnectionWithTimeout)
def test_close():
http = httplib2.Http()
assert len(http.connections) == 0
with tests.server_const_http() as uri:
http.request(uri)
assert len(http.connections) == 1
http.close()
assert len(http.connections) == 0
def test_connect_exception_type():
# This autoformatting PR actually changed the behavior of error handling:
# https://github.com/httplib2/httplib2/pull/105/files#diff-c6669c781a2dee1b2d2671cab4e21c66L985
# potentially changing the type of the error raised by connect()
# https://github.com/httplib2/httplib2/pull/150
http = httplib2.Http()
with mock.patch("httplib2.socket.socket.connect", side_effect=socket.timeout("foo")):
with tests.assert_raises(socket.timeout):
http.request(tests.DUMMY_URL)