mirror of
https://github.com/bohd4nx/FragmentAPI.git
synced 2026-07-25 06:14:29 +00:00
fix: remove unnecessary 'method' key from API request data and improve error handling for expired cookies and non-200 HTTP responses
This commit is contained in:
@@ -45,7 +45,7 @@ async def search_gifts(
|
||||
FragmentAPIError: If the Fragment API returns an error.
|
||||
UnexpectedError: For any other unexpected failure.
|
||||
"""
|
||||
data: dict[str, Any] = {"method": "searchAuctions", "type": "gifts", "query": query}
|
||||
data: dict[str, Any] = {"type": "gifts", "query": query}
|
||||
if collection is not None:
|
||||
data["collection"] = collection
|
||||
if sort is not None:
|
||||
|
||||
@@ -37,7 +37,7 @@ async def search_numbers(
|
||||
FragmentAPIError: If the Fragment API returns an error.
|
||||
UnexpectedError: For any other unexpected failure.
|
||||
"""
|
||||
data: dict[str, Any] = {"method": "searchAuctions", "type": "numbers", "query": query}
|
||||
data: dict[str, Any] = {"type": "numbers", "query": query}
|
||||
if sort is not None:
|
||||
data["sort"] = sort
|
||||
if filter is not None:
|
||||
|
||||
@@ -37,7 +37,7 @@ async def search_usernames(
|
||||
FragmentAPIError: If the Fragment API returns an error.
|
||||
UnexpectedError: For any other unexpected failure.
|
||||
"""
|
||||
data: dict[str, Any] = {"method": "searchAuctions", "type": "usernames", "query": query}
|
||||
data: dict[str, Any] = {"type": "usernames", "query": query}
|
||||
if sort is not None:
|
||||
data["sort"] = sort
|
||||
if filter is not None:
|
||||
|
||||
@@ -46,6 +46,7 @@ class CookieError(ClientError):
|
||||
"Fragment cookies not found in {browser}: {keys}. "
|
||||
"Make sure you are logged in to {url} and have connected your TON wallet in {browser}."
|
||||
)
|
||||
EXPIRED = "Fragment session cookie expired at {expires}. Log in to fragment.com in your browser and extract fresh cookies."
|
||||
|
||||
|
||||
class FragmentAPIError(FragmentError):
|
||||
|
||||
@@ -61,6 +61,11 @@ def get_cookies_from_browser(browser: str = "chrome") -> CookieResult:
|
||||
continue
|
||||
break
|
||||
|
||||
if expires_iso:
|
||||
expires_dt = datetime.fromisoformat(expires_iso)
|
||||
if expires_dt < datetime.now(timezone.utc):
|
||||
raise CookieError(CookieError.EXPIRED.format(expires=expires_iso))
|
||||
|
||||
return CookieResult(
|
||||
cookies={k: cookie_map[k] for k in REQUIRED_COOKIE_KEYS},
|
||||
expires=expires_iso,
|
||||
|
||||
@@ -111,6 +111,8 @@ async def fragment_request(
|
||||
headers=headers,
|
||||
data=data,
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
raise FragmentPageError(FragmentPageError.BAD_STATUS.format(status=resp.status_code, url=f"{FRAGMENT_BASE_URL}/api"))
|
||||
return parse_json_response(resp, data.get("method", "request"))
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ async def process_transaction(client: FragmentClient, transaction_data: dict[str
|
||||
TransactionError: If the payload is malformed or the broadcast fails.
|
||||
WalletError: If the wallet balance is too low or cannot be fetched.
|
||||
"""
|
||||
if "transaction" not in transaction_data or "messages" not in transaction_data["transaction"]:
|
||||
if "transaction" not in transaction_data or not transaction_data["transaction"].get("messages"):
|
||||
raise TransactionError(TransactionError.INVALID_PAYLOAD)
|
||||
|
||||
message = transaction_data["transaction"]["messages"][0]
|
||||
|
||||
@@ -103,6 +103,12 @@ async def test_invalid_payload_raises() -> None:
|
||||
await process_transaction(_make_client(), {"transaction": {}})
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_messages_list_raises() -> None:
|
||||
with pytest.raises(TransactionError):
|
||||
await process_transaction(_make_client(), {"transaction": {"messages": []}})
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_balance_check_failed_raises_wallet_error() -> None:
|
||||
wallet = _make_wallet(balance_nanotons=1_000_000_000)
|
||||
|
||||
+20
-1
@@ -1,10 +1,13 @@
|
||||
"""Unit tests for FragmentClient.call() — raw Fragment API access."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from pyfragment import FragmentClient
|
||||
from pyfragment.types import FragmentPageError
|
||||
from pyfragment.utils.http import fragment_request
|
||||
from tests.shared import FAKE_HASH, FAKE_RESPONSE
|
||||
|
||||
# client.call() mocked tests
|
||||
@@ -61,3 +64,19 @@ async def test_call_merges_extra_data(client: FragmentClient) -> None:
|
||||
|
||||
_, _, _, sent_data = mock_request.call_args.args
|
||||
assert sent_data == {"method": "anyMethod", "key": "value", "num": 7}
|
||||
|
||||
|
||||
# fragment_request HTTP status tests
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fragment_request_non_200_raises() -> None:
|
||||
"""fragment_request raises FragmentPageError on non-200 HTTP responses."""
|
||||
response = MagicMock(spec=httpx.Response)
|
||||
response.status_code = 429
|
||||
|
||||
session = AsyncMock(spec=httpx.AsyncClient)
|
||||
session.post = AsyncMock(return_value=response)
|
||||
|
||||
with pytest.raises(FragmentPageError, match="429"):
|
||||
await fragment_request(session, FAKE_HASH, {}, {"method": "anyMethod"})
|
||||
|
||||
@@ -106,6 +106,21 @@ def test_empty_cookie_value_treated_as_missing() -> None:
|
||||
get_cookies_from_browser("chrome")
|
||||
|
||||
|
||||
# expired cookie tests
|
||||
|
||||
|
||||
def test_expired_cookie_raises() -> None:
|
||||
expired_jar = [
|
||||
{"name": "stel_ssid", "value": "abc123", "expires": "2020-01-01T00:00:00.000Z"},
|
||||
{"name": "stel_dt", "value": "-120"},
|
||||
{"name": "stel_token", "value": "tok_xyz"},
|
||||
{"name": "stel_ton_token", "value": "ton_xyz"},
|
||||
]
|
||||
with patch(PATCH, _mock_rookiepy(expired_jar)):
|
||||
with pytest.raises(CookieError, match="expired"):
|
||||
get_cookies_from_browser("chrome")
|
||||
|
||||
|
||||
# read failure tests
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user