Files
wehub-resource-sync 6ede33ccdb
Build and Push Docker Images / create_manifest (web, surfsense-web, , cpu) (push) Has been cancelled
Build and Push Docker Images / finalize_release (push) Has been cancelled
Obsidian Plugin Lint / lint (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / compute_version (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / verify_digests (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, , cpu) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda, cuda) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda126, cuda126) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:33:44 +08:00

163 lines
3.7 KiB
Python

"""Tests for async_retry utilities."""
import httpx
import pytest
from app.connectors.exceptions import (
ConnectorAPIError,
ConnectorAuthError,
ConnectorError,
ConnectorRateLimitError,
ConnectorTimeoutError,
)
from app.utils.async_retry import _is_retryable, raise_for_status
pytestmark = pytest.mark.unit
def make_response(
status_code: int,
*,
headers: dict[str, str] | None = None,
json_body=None,
text_body: str = "",
):
kwargs = {
"status_code": status_code,
"headers": headers,
"request": httpx.Request("GET", "https://x"),
}
if json_body is not None:
kwargs["json"] = json_body
else:
kwargs["text"] = text_body
return httpx.Response(**kwargs)
def test_raise_for_status_does_not_raise_for_success():
response = make_response(200)
raise_for_status(response)
@pytest.mark.parametrize(
("retry_after_header", "expected"),
[
("5", 5.0),
(None, None),
("abc", None),
],
)
def test_raise_for_status_429(retry_after_header, expected):
headers = {}
if retry_after_header is not None:
headers["Retry-After"] = retry_after_header
response = make_response(
429,
headers=headers,
json_body={"detail": "rate limited"},
)
with pytest.raises(ConnectorRateLimitError) as exc_info:
raise_for_status(response)
exc = exc_info.value
assert exc.retry_after == expected
assert exc.response_body == {"detail": "rate limited"}
@pytest.mark.parametrize("status_code", [401, 403])
def test_raise_for_status_auth_errors(status_code):
response = make_response(
status_code,
json_body={"error": "unauthorized"},
)
with pytest.raises(ConnectorAuthError) as exc_info:
raise_for_status(response)
exc = exc_info.value
assert exc.status_code == status_code
assert exc.response_body == {"error": "unauthorized"}
def test_raise_for_status_gateway_timeout():
response = make_response(
504,
json_body={"error": "timeout"},
)
with pytest.raises(ConnectorTimeoutError):
raise_for_status(response)
@pytest.mark.parametrize("status_code", [500, 502])
def test_raise_for_status_server_errors(status_code):
response = make_response(
status_code,
json_body={"error": "server"},
)
with pytest.raises(ConnectorAPIError) as exc_info:
raise_for_status(response)
assert exc_info.value.status_code == status_code
@pytest.mark.parametrize("status_code", [400, 404])
def test_raise_for_status_client_errors(status_code):
response = make_response(
status_code,
json_body={"error": "client"},
)
with pytest.raises(ConnectorAPIError) as exc_info:
raise_for_status(response)
assert exc_info.value.status_code == status_code
def test_raise_for_status_uses_text_when_json_parsing_fails():
response = make_response(
500,
text_body="Internal server error",
)
with pytest.raises(ConnectorAPIError) as exc_info:
raise_for_status(response)
assert exc_info.value.response_body == "Internal server error"
def test_connector_error_retryable_false():
exc = ConnectorError("boom")
assert _is_retryable(exc) is False
def test_rate_limit_error_is_retryable():
exc = ConnectorRateLimitError()
assert _is_retryable(exc) is True
def test_timeout_exception_is_retryable():
exc = httpx.TimeoutException("timeout")
assert _is_retryable(exc) is True
def test_connect_error_is_retryable():
exc = httpx.ConnectError("connection failed")
assert _is_retryable(exc) is True
def test_unrelated_exception_is_not_retryable():
exc = ValueError("boom")
assert _is_retryable(exc) is False