Add respx for better testing, fix HTTP 4XX handling
parent
a0864e413a
commit
65cdc9ada7
|
@ -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]
|
||||
|
||||
|
|
2
Pipfile
2
Pipfile
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue