Add respx for better testing, fix HTTP 4XX handling

This commit is contained in:
SporeStack 2023-04-14 22:45:43 +00:00
parent a0864e413a
commit 65cdc9ada7
6 changed files with 156 additions and 31 deletions

View File

@ -11,7 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Nothing yet.
## Fixed
- HTTP 4XX errors now raise a `SporeStackUserError` instead of `SporeStackServerError`.
## [10.2.0 - 2023-04-14]

View File

@ -15,7 +15,7 @@ pytest-mock = "~=3.6"
pytest-socket = "~=0.6.0"
ruff = "==0.0.261"
types-requests = "~=2.25"
respx = "~=0.20.1"
# Building
flit = "~=3.8"

87
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "ad9d790601f514743ccca149f2c41fce3db8493e9386372bb8416af64a06eebf"
"sha256": "9d77640a42b679eb979a9e2ed727e532aa1e4239ad482c5583dc2069c6731935"
},
"pipfile-spec": 6,
"requires": {},
@ -168,6 +168,14 @@
"index": "pypi",
"version": "==0.5.2"
},
"anyio": {
"hashes": [
"sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421",
"sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"
],
"markers": "python_full_version >= '3.6.2'",
"version": "==3.6.2"
},
"black": {
"hashes": [
"sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5",
@ -396,6 +404,33 @@
"markers": "python_version >= '3.6'",
"version": "==3.8.0"
},
"h11": {
"hashes": [
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
],
"markers": "python_version >= '3.7'",
"version": "==0.14.0"
},
"httpcore": {
"hashes": [
"sha256:0fdfea45e94f0c9fd96eab9286077f9ff788dd186635ae61b312693e4d943599",
"sha256:cc045a3241afbf60ce056202301b4d8b6af08845e3294055eb26b09913ef903c"
],
"markers": "python_version >= '3.7'",
"version": "==0.17.0"
},
"httpx": {
"extras": [
"socks"
],
"hashes": [
"sha256:447556b50c1921c351ea54b4fe79d91b724ed2b027462ab9a329465d147d5a4e",
"sha256:507d676fc3e26110d41df7d35ebd8b3b8585052450f4097401c9be59d928c63e"
],
"markers": "python_version >= '3.7'",
"version": "==0.24.0"
},
"idna": {
"hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
@ -566,11 +601,11 @@
},
"packaging": {
"hashes": [
"sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
"sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
"sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
"sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
"markers": "python_version >= '3.7'",
"version": "==23.0"
"version": "==23.1"
},
"pathspec": {
"hashes": [
@ -630,11 +665,11 @@
},
"pytest": {
"hashes": [
"sha256:58ecc27ebf0ea643ebfdf7fb1249335da761a00c9f955bcd922349bcb68ee57d",
"sha256:933051fa1bfbd38a21e73c3960cebdad4cf59483ddba7696c48509727e17f201"
"sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362",
"sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"
],
"index": "pypi",
"version": "==7.3.0"
"version": "==7.3.1"
},
"pytest-cov": {
"hashes": [
@ -684,8 +719,15 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.1"
},
"respx": {
"hashes": [
"sha256:372f06991c03d1f7f480a420a2199d01f1815b6ed5a802f4e4628043a93bd03e",
"sha256:cc47a86d7010806ab65abdcf3b634c56337a737bb5c4d74c19a0dfca83b3bc73"
],
"index": "pypi",
"version": "==0.20.1"
},
"rfc3986": {
"extras": [],
"hashes": [
"sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd",
"sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"
@ -695,11 +737,11 @@
},
"rich": {
"hashes": [
"sha256:540c7d6d26a1178e8e8b37e9ba44573a3cd1464ff6348b99ee7061b95d1c6333",
"sha256:dc84400a9d842b3a9c5ff74addd8eb798d155f36c1c91303888e0a66850d2a15"
"sha256:22b74cae0278fd5086ff44144d3813be1cedc9115bdfabbfefd86400cb88b20a",
"sha256:b5d573e13605423ec80bdd0cd5f8541f7844a0e71a13f74cf454ccb2f490708b"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==13.3.3"
"version": "==13.3.4"
},
"ruff": {
"hashes": [
@ -732,6 +774,14 @@
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sniffio": {
"hashes": [
"sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
"sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.0"
},
"tomli-w": {
"hashes": [
"sha256:9f2a07e8be30a0729e533ec968016807069991ae2fd921a78d42f429ae5f4463",
@ -748,21 +798,6 @@
"index": "pypi",
"version": "==4.0.2"
},
"types-requests": {
"hashes": [
"sha256:0d580652ce903f643f8c3b494dd01d29367ea57cea0c7ad7f65cf3169092edb0",
"sha256:cc1aba862575019306b2ed134eb1ea994cab1c887a22e18d3383e6dd42e9789b"
],
"index": "pypi",
"version": "==2.28.11.17"
},
"types-urllib3": {
"hashes": [
"sha256:12c744609d588340a07e45d333bf870069fc8793bcf96bae7a96d4712a42591d",
"sha256:c44881cde9fc8256d05ad6b21f50c4681eb20092552351570ab0a8a0653286d6"
],
"version": "==1.26.25.10"
},
"typing-extensions": {
"hashes": [
"sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",

View File

@ -10,6 +10,7 @@
* `pip install sporestack`
* Recommended: Create a virtual environment, first, and use it inside there.
* Something else to consider: Installing [rich](https://github.com/Textualize/rich) (`pip install rich`) in the same virtual environment will make `--help`-style output prettier.
## Running without installing

View File

@ -84,12 +84,12 @@ def _handle_response(response: httpx.Response) -> None:
if response.status_code == 429:
raise exceptions.SporeStackTooManyRequestsError(error_response_text)
elif status_code_first_digit == 4:
raise exceptions.SporeStackServerError(error_response_text)
raise exceptions.SporeStackUserError(error_response_text)
elif status_code_first_digit == 5:
# User should probably retry.
raise exceptions.SporeStackServerError(error_response_text)
else:
# How did we get here?
# This would be weird.
raise exceptions.SporeStackServerError(error_response_text)

View File

@ -1,4 +1,7 @@
from sporestack import api_client
import httpx
import pytest
import respx
from sporestack import api_client, exceptions
def test__is_onion_url() -> None:
@ -13,3 +16,87 @@ def test__is_onion_url() -> None:
assert api_client._is_onion_url("http://onion.domain.com/.onion/") is False
assert api_client._is_onion_url("http://me.me/file.onion/") is False
assert api_client._is_onion_url("http://me.me/file.onion") is False
def test_get_response_error_text() -> None:
assert (
api_client._get_response_error_text(
httpx.Response(status_code=422, text="just text")
)
== "just text"
)
assert (
api_client._get_response_error_text(
httpx.Response(status_code=422, json={"detail": "detail text"})
)
== "detail text"
)
# This may not be the best behavior overall.
assert (
api_client._get_response_error_text(
httpx.Response(status_code=422, json={"detail": {"msg": "nested message"}})
)
== "{'msg': 'nested message'}"
)
def test_handle_response() -> None:
with pytest.raises(exceptions.SporeStackServerError, match="What is this?"):
api_client._handle_response(
httpx.Response(status_code=100, text="What is this?")
)
api_client._handle_response(httpx.Response(status_code=200))
api_client._handle_response(httpx.Response(status_code=201))
api_client._handle_response(httpx.Response(status_code=204))
with pytest.raises(exceptions.SporeStackUserError, match="Invalid arguments"):
api_client._handle_response(
httpx.Response(status_code=400, text="Invalid arguments")
)
with pytest.raises(exceptions.SporeStackUserError, match="Invalid arguments"):
api_client._handle_response(
httpx.Response(status_code=422, text="Invalid arguments")
)
with pytest.raises(
exceptions.SporeStackTooManyRequestsError, match="Too many requests"
):
api_client._handle_response(
httpx.Response(status_code=429, text="Too many requests")
)
with pytest.raises(exceptions.SporeStackServerError, match="Try again"):
api_client._handle_response(httpx.Response(status_code=500, text="Try again"))
def test_token_info(respx_mock: respx.MockRouter) -> None:
dummy_token = "dummyinvalidtoken"
response_json = {
"balance_cents": 0,
"balance_usd": "$0.00",
"servers": 0,
"burn_rate": 0,
"burn_rate_usd": "$0.00",
"burn_rate_cents": 0,
"days_remaining": 0,
}
route_response = httpx.Response(200, json=response_json)
route = respx_mock.get(f"https://api.sporestack.com/token/{dummy_token}/info").mock(
return_value=route_response
)
client = api_client.APIClient()
info_response = client.token_info(dummy_token)
assert info_response.balance_cents == 0
assert info_response.balance_usd == "$0.00"
assert info_response.burn_rate == 0
assert info_response.burn_rate_cents == 0
assert info_response.burn_rate_usd == "$0.00"
assert info_response.servers == 0
assert info_response.days_remaining == 0
assert route.called