mirror of
https://github.com/vibe-existing/pyfragment.git
synced 2026-07-25 06:54:31 +00:00
3e14a01c92
- 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
92 lines
3.2 KiB
Python
92 lines
3.2 KiB
Python
"""Unit tests for process_transaction() — balance checks before broadcast."""
|
|
|
|
from contextlib import contextmanager
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from pyfragment.types import TransactionError, WalletError
|
|
from pyfragment.utils.wallet import process_transaction
|
|
|
|
VALID_SEED = "abandon " * 23 + "about"
|
|
|
|
TRANSACTION_DATA = {
|
|
"transaction": {
|
|
"messages": [
|
|
{
|
|
"address": "0:852443f8599fe6a5da34fe43049ac4e0beb3071bb2bfb56635ea9421287c283a",
|
|
"amount": "500000000", # 0.5 TON
|
|
"payload": "",
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
|
|
def _make_client() -> MagicMock:
|
|
client = MagicMock()
|
|
client.api_key = "test_key"
|
|
client.seed = VALID_SEED.split()
|
|
client.wallet_version = "V5R1"
|
|
return client
|
|
|
|
|
|
def _make_wallet(balance_nanotons: int) -> MagicMock:
|
|
wallet = MagicMock()
|
|
wallet.refresh = AsyncMock()
|
|
wallet.balance = balance_nanotons
|
|
wallet.transfer = AsyncMock(return_value=MagicMock(normalized_hash="abc123"))
|
|
return wallet
|
|
|
|
|
|
@contextmanager
|
|
def _patch_wallet(wallet: MagicMock):
|
|
with (
|
|
patch("pyfragment.utils.wallet.TonapiClient") as mock_tonapi,
|
|
patch("pyfragment.utils.wallet.WALLET_CLASSES") as mock_classes,
|
|
):
|
|
mock_tonapi.return_value.__aenter__ = AsyncMock(return_value=MagicMock())
|
|
mock_tonapi.return_value.__aexit__ = AsyncMock(return_value=False)
|
|
mock_classes["V5R1"].from_mnemonic.return_value = (wallet, MagicMock(), None, None)
|
|
yield
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sufficient_balance_broadcasts() -> None:
|
|
wallet = _make_wallet(balance_nanotons=1_000_000_000) # 1 TON, needs 0.556 TON
|
|
with _patch_wallet(wallet), patch("pyfragment.utils.wallet.clean_decode", return_value="50 Telegram Stars"):
|
|
result = await process_transaction(_make_client(), TRANSACTION_DATA)
|
|
assert result == "abc123"
|
|
wallet.transfer.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_insufficient_balance_raises() -> None:
|
|
wallet = _make_wallet(balance_nanotons=100_000_000) # 0.1 TON, needs 0.556 TON
|
|
with _patch_wallet(wallet):
|
|
with pytest.raises(WalletError, match="required"):
|
|
await process_transaction(_make_client(), TRANSACTION_DATA)
|
|
wallet.transfer.assert_not_called()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_exact_minimum_balance_broadcasts() -> None:
|
|
wallet = _make_wallet(balance_nanotons=556_000_000) # exactly 0.5 + 0.056 TON
|
|
with _patch_wallet(wallet), patch("pyfragment.utils.wallet.clean_decode", return_value="50 Telegram Stars"):
|
|
result = await process_transaction(_make_client(), TRANSACTION_DATA)
|
|
assert result == "abc123"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_one_nanoton_below_minimum_raises() -> None:
|
|
wallet = _make_wallet(balance_nanotons=555_999_999) # 1 nanoton below threshold
|
|
with _patch_wallet(wallet):
|
|
with pytest.raises(WalletError, match="required"):
|
|
await process_transaction(_make_client(), TRANSACTION_DATA)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invalid_payload_raises() -> None:
|
|
with pytest.raises(TransactionError):
|
|
await process_transaction(_make_client(), {"transaction": {}})
|