-
Notifications
You must be signed in to change notification settings - Fork 49
Expand file tree
/
Copy pathtest_http_client.py
More file actions
145 lines (120 loc) · 4.97 KB
/
Copy pathtest_http_client.py
File metadata and controls
145 lines (120 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""Tests for the ``http_client`` helper module."""
from __future__ import annotations
import unittest.mock as mock
import pytest
import requests
from requests.adapters import HTTPAdapter
from release_service_utils.helpers import http_client
def test_retries_policy_defaults() -> None:
"""The shared retry config matches the module defaults."""
r = http_client._retries()
assert r.total == 3
assert r.connect == 3
assert r.read == 3
assert r.status == 2
assert r.backoff_factor == 0.4
assert r.raise_on_status is False
assert set(r.status_forcelist) == {500, 502, 503, 504}
assert set(r.allowed_methods) == {"GET"}
def test_get_session_mounts_retry_adapter() -> None:
"""`get_session` mounts an `HTTPAdapter` for both HTTP schemes."""
s = http_client.get_session()
https_adapter = s.adapters["https://"]
http_adapter = s.adapters["http://"]
assert isinstance(https_adapter, HTTPAdapter)
assert isinstance(http_adapter, HTTPAdapter)
assert https_adapter.max_retries.total == 3
assert http_adapter.max_retries.total == 3
def test_get_text_uses_session_and_headers() -> None:
"""``get_text`` passes *headers* through to ``get_session``’s ``.get`` call."""
session = mock.MagicMock()
r = mock.MagicMock()
r.status_code = 200
r.text = "body"
r.raise_for_status = mock.MagicMock()
session.get.return_value = r
with mock.patch(
"release_service_utils.helpers.http_client.get_session", return_value=session
):
out = http_client.get_text("https://e/x", headers={"A": "b", "C": "d"})
assert out == "body"
assert session.get.call_count == 1
cargs, ckw = session.get.call_args
assert cargs[0] == "https://e/x"
assert ckw["headers"] == {"A": "b", "C": "d"}
assert ckw["auth"] is None
r.raise_for_status.assert_not_called()
def test_get_text_auth_passed() -> None:
"""*auth* is passed through (e.g. for SPNEGO)."""
session = mock.MagicMock()
r = mock.MagicMock()
r.status_code = 200
r.text = "t"
session.get.return_value = r
ra = object()
with mock.patch(
"release_service_utils.helpers.http_client.get_session", return_value=session
):
assert http_client.get_text("https://u", auth=ra) == "t"
assert session.get.call_args[1]["auth"] is ra
def test_get_text_http_error() -> None:
"""A failed HTTP status raises ``requests.HTTPError`` (like ``curl --fail``)."""
session = mock.MagicMock()
r = mock.MagicMock()
r.status_code = 500
r.raise_for_status.side_effect = requests.HTTPError("nope", response=mock.MagicMock())
session.get.return_value = r
with mock.patch(
"release_service_utils.helpers.http_client.get_session", return_value=session
):
with pytest.raises(requests.HTTPError):
http_client.get_text("https://u/")
def test_get_text_retries_429_then_succeeds() -> None:
"""HTTP 429 is retried with backoff; a later 2xx response returns body text."""
session = mock.MagicMock()
r1 = mock.MagicMock()
r1.status_code = 429
r1.text = "rate-limited-1"
r1.raise_for_status.side_effect = requests.HTTPError("429", response=mock.MagicMock())
r2 = mock.MagicMock()
r2.status_code = 429
r2.text = "rate-limited-2"
r2.raise_for_status.side_effect = requests.HTTPError("429", response=mock.MagicMock())
r3 = mock.MagicMock()
r3.status_code = 200
r3.text = "ok"
r3.raise_for_status = mock.MagicMock()
session.get.side_effect = [r1, r2, r3]
with (
mock.patch(
"release_service_utils.helpers.http_client.get_session", return_value=session
),
mock.patch("release_service_utils.helpers.http_client.random.randint", return_value=0),
mock.patch("release_service_utils.helpers.http_client.time.sleep") as sleep_mock,
):
assert http_client.get_text("https://u") == "ok"
assert session.get.call_count == 3
sleep_mock.assert_has_calls([mock.call(1), mock.call(2)])
def test_get_text_retries_404_when_enabled(monkeypatch: pytest.MonkeyPatch) -> None:
"""HTTP 404 retries happen only when `CURL_WITH_RETRY_RETRY_404` is set."""
monkeypatch.setenv("CURL_WITH_RETRY_RETRY_404", "1")
session = mock.MagicMock()
r1 = mock.MagicMock()
r1.status_code = 404
r1.text = "not-found"
r1.raise_for_status.side_effect = requests.HTTPError("404", response=mock.MagicMock())
r2 = mock.MagicMock()
r2.status_code = 200
r2.text = "ok"
r2.raise_for_status = mock.MagicMock()
session.get.side_effect = [r1, r2]
with (
mock.patch(
"release_service_utils.helpers.http_client.get_session", return_value=session
),
mock.patch("release_service_utils.helpers.http_client.random.randint", return_value=0),
mock.patch("release_service_utils.helpers.http_client.time.sleep") as sleep_mock,
):
assert http_client.get_text("https://u") == "ok"
assert session.get.call_count == 2
sleep_mock.assert_called_once_with(1)