mirror of
https://github.com/vibe-existing/pyfragment.git
synced 2026-07-25 06:54:31 +00:00
4e03d2e2f6
- 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
147 lines
5.8 KiB
Python
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)
|