Files
pyfragment/tests/006_test_methods_mock.py
T
bohd4nx 3e14a01c92 refactor: harden client, clean tests, and fix timeouts
- Security & validation: tighten hash regex, add HTTP timeouts to all
  requests, pass client.timeout through get_fragment_hash and AsyncClient
- Constants: move all constants to types/constants.py; remove re-exports
  from types/__init__.py; add DEFAULT_TIMEOUT, REQUIRED_COOKIE_KEYS
- FragmentClient: add timeout param (default 30 s); async-context-manager
  support; remove WALLET_CLASSES from public API
- Exceptions: remove dead INVALID_USERNAME constant (Fragment validates
  server-side); keep full hierarchy intact
- Tests: add 006_test_methods_mock.py (6 mock tests for all 3 methods);
  DRY-refactor 004_test_balance.py (_patch_wallet context manager);
  clean up 005_test_methods.py (remove fragile network test, rename tests)
- Examples: switch all 4 examples to async-with; align error messages;
  replace %-format with f-strings
- README: rewrite usage section with single comprehensive async-with
  example covering all 3 methods and full exception hierarchy
- CI: add mypy step to lint job; add pytest-mock and mypy to dev deps;
  set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 on all jobs; fix COOKIES_JSON
  to job-level env var
2026-03-20 20:16:43 +02:00

147 lines
5.8 KiB
Python

"""Tests for purchase methods with all network calls mocked."""
from unittest.mock import AsyncMock, patch
import pytest
from pyfragment import FragmentClient
from pyfragment.types import AdsTopupResult, PremiumResult, StarsResult, UserNotFoundError
VALID_SEED = "abandon " * 23 + "about"
VALID_API_KEY = "A" * 68
VALID_COOKIES = {
"stel_ssid": "x",
"stel_dt": "x",
"stel_token": "x",
"stel_ton_token": "x",
}
FAKE_HASH = "abc123"
FAKE_RECIPIENT = "recipient_token"
FAKE_REQ_ID = "req_42"
FAKE_TX_HASH = "deadbeef" * 8
FAKE_ACCOUNT = {"address": "0:abc", "publicKey": "pub", "chain": "-239", "walletStateInit": "base64=="}
FAKE_TRANSACTION = {"transaction": {"messages": [{"address": "0:abc", "amount": "100000000", "payload": ""}]}}
@pytest.fixture
def client() -> FragmentClient:
return FragmentClient(seed=VALID_SEED, api_key=VALID_API_KEY, cookies=VALID_COOKIES)
@pytest.mark.asyncio
async def test_purchase_stars_success(client: FragmentClient) -> None:
with (
patch("pyfragment.methods.stars.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)),
patch("pyfragment.methods.stars.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)),
patch(
"pyfragment.methods.stars.fragment_post",
AsyncMock(
side_effect=[
{"found": {"recipient": FAKE_RECIPIENT}}, # searchStarsRecipient
{"req_id": FAKE_REQ_ID}, # initBuyStarsRequest
]
),
),
patch("pyfragment.methods.stars.execute_transaction_request", AsyncMock(return_value=FAKE_TRANSACTION)),
patch("pyfragment.methods.stars.process_transaction", AsyncMock(return_value=FAKE_TX_HASH)),
):
result = await client.purchase_stars("testuser", amount=100)
assert isinstance(result, StarsResult)
assert result.transaction_id == FAKE_TX_HASH
assert result.username == "testuser"
assert result.stars == 100
@pytest.mark.asyncio
async def test_purchase_stars_user_not_found(client: FragmentClient) -> None:
with (
patch("pyfragment.methods.stars.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)),
patch("pyfragment.methods.stars.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)),
patch("pyfragment.methods.stars.fragment_post", AsyncMock(return_value={"found": {}})),
):
with pytest.raises(UserNotFoundError):
await client.purchase_stars("ghost", amount=100)
@pytest.mark.asyncio
async def test_purchase_premium_success(client: FragmentClient) -> None:
with (
patch("pyfragment.methods.premium.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)),
patch("pyfragment.methods.premium.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)),
patch(
"pyfragment.methods.premium.fragment_post",
AsyncMock(
side_effect=[
{"found": {"recipient": FAKE_RECIPIENT}}, # searchPremiumGiftRecipient
{}, # updatePremiumState
{"req_id": FAKE_REQ_ID}, # initGiftPremiumRequest
]
),
),
patch("pyfragment.methods.premium.execute_transaction_request", AsyncMock(return_value=FAKE_TRANSACTION)),
patch("pyfragment.methods.premium.process_transaction", AsyncMock(return_value=FAKE_TX_HASH)),
):
result = await client.purchase_premium("testuser", months=6)
assert isinstance(result, PremiumResult)
assert result.transaction_id == FAKE_TX_HASH
assert result.username == "testuser"
assert result.months == 6
@pytest.mark.asyncio
async def test_purchase_premium_user_not_found(client: FragmentClient) -> None:
with (
patch("pyfragment.methods.premium.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)),
patch("pyfragment.methods.premium.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)),
patch("pyfragment.methods.premium.fragment_post", AsyncMock(return_value={"found": {}})),
):
with pytest.raises(UserNotFoundError):
await client.purchase_premium("ghost", months=3)
@pytest.mark.asyncio
async def test_topup_ton_success(client: FragmentClient) -> None:
with (
patch("pyfragment.methods.ton.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)),
patch("pyfragment.methods.ton.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)),
patch(
"pyfragment.methods.ton.fragment_post",
AsyncMock(
side_effect=[
{}, # updateAdsTopupState
{"found": {"recipient": FAKE_RECIPIENT}}, # searchAdsTopupRecipient
{"req_id": FAKE_REQ_ID}, # initAdsTopupRequest
]
),
),
patch("pyfragment.methods.ton.execute_transaction_request", AsyncMock(return_value=FAKE_TRANSACTION)),
patch("pyfragment.methods.ton.process_transaction", AsyncMock(return_value=FAKE_TX_HASH)),
):
result = await client.topup_ton("testuser", amount=10)
assert isinstance(result, AdsTopupResult)
assert result.transaction_id == FAKE_TX_HASH
assert result.username == "testuser"
assert result.amount == 10
@pytest.mark.asyncio
async def test_topup_ton_user_not_found(client: FragmentClient) -> None:
with (
patch("pyfragment.methods.ton.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)),
patch("pyfragment.methods.ton.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)),
patch(
"pyfragment.methods.ton.fragment_post",
AsyncMock(
side_effect=[
{}, # updateAdsTopupState
{"found": {}}, # searchAdsTopupRecipient → not found
]
),
),
):
with pytest.raises(UserNotFoundError):
await client.topup_ton("ghost", amount=10)