|
1 | 1 | import base64 |
2 | 2 | import threading |
3 | 3 | import time |
| 4 | +import traceback |
4 | 5 | from concurrent.futures import ThreadPoolExecutor |
5 | 6 | from urllib.parse import parse_qs |
6 | 7 |
|
@@ -223,3 +224,58 @@ def test_downstream_gzip_response_remains_readable(https_server, chunked): |
223 | 224 |
|
224 | 225 | response = subject.request("GET", https_server.url + "/inventory") |
225 | 226 | assert response.json() == {"items": [123]} |
| 227 | + |
| 228 | + |
| 229 | +@pytest.mark.parametrize(("method", "status"), [("GET", 200), ("GET", 204), ("GET", 304), ("HEAD", 200)]) |
| 230 | +def test_empty_downstream_responses_preserve_bytes_and_status(https_server, method, status): |
| 231 | + https_server.serve("/token", TOKEN_RESPONSE) |
| 232 | + https_server.serve("/inventory", b"not sent for HEAD" if method == "HEAD" else b"", status=status) |
| 233 | + subject = client(https_server) |
| 234 | + |
| 235 | + response = subject.request(method, https_server.url + "/inventory") |
| 236 | + |
| 237 | + assert response.status == status |
| 238 | + assert response.data == b"" |
| 239 | + assert response.data.decode() == "" |
| 240 | + assert [request[1] for request in https_server.requests] == ["/token", "/inventory"] |
| 241 | + |
| 242 | + |
| 243 | +@pytest.mark.parametrize("endpoint", ["token", "inventory"]) |
| 244 | +def test_malformed_response_headers_fail_without_logging_credentials(https_server, caplog, endpoint): |
| 245 | + private_data = "Bearer local-test-private-token" |
| 246 | + https_server.serve("/token", TOKEN_RESPONSE) |
| 247 | + https_server.serve("/inventory", {"items": [123]}) |
| 248 | + payload = TOKEN_RESPONSE if endpoint == "token" else {"items": [123]} |
| 249 | + https_server.serve(f"/{endpoint}", payload, headers={"Broken header": private_data}) |
| 250 | + subject = client(https_server) |
| 251 | + expected = TokenExchangeError if endpoint == "token" else DownstreamRequestError |
| 252 | + |
| 253 | + with pytest.raises(expected) as error: |
| 254 | + subject.request("GET", https_server.url + "/inventory") |
| 255 | + |
| 256 | + assert not error.value.retryable |
| 257 | + assert error.value.__context__ is None |
| 258 | + assert error.value.__cause__ is None |
| 259 | + assert private_data not in "".join(traceback.format_exception(error.value)) |
| 260 | + assert private_data not in caplog.text |
| 261 | + assert not [record for record in caplog.records if record.name == "urllib3.connection"] |
| 262 | + expected_paths = ["/token"] if endpoint == "token" else ["/token", "/inventory"] |
| 263 | + assert [request[1] for request in https_server.requests] == expected_paths |
| 264 | + |
| 265 | + https_server.serve(f"/{endpoint}", payload) |
| 266 | + assert subject.request("GET", https_server.url + "/inventory").json() == {"items": [123]} |
| 267 | + |
| 268 | + |
| 269 | +def test_valid_extended_header_values_are_sent_over_tls(https_server): |
| 270 | + https_server.serve("/token", TOKEN_RESPONSE) |
| 271 | + https_server.serve("/inventory", {"items": [123]}) |
| 272 | + value = "caf\xe9\t\x80\xff" |
| 273 | + |
| 274 | + response = client(https_server).request( |
| 275 | + "GET", |
| 276 | + https_server.url + "/inventory", |
| 277 | + headers={"X-Trace": value}, |
| 278 | + ) |
| 279 | + |
| 280 | + assert response.status == 200 |
| 281 | + assert https_server.requests[-1][2]["X-Trace"] == value |
0 commit comments