From ab8d463d54847cc74cf5257fa28d5cb5f879aea9 Mon Sep 17 00:00:00 2001 From: bohd4nx Date: Sat, 21 Mar 2026 17:18:56 +0200 Subject: [PATCH] feat: add unit tests for balance checks, giveaway stars, and premium methods; validate float inputs for winners and amounts --- tests/004_test_balance.py | 37 +++++++++++++++++++++++++++++++++++++ tests/005_test_stars.py | 12 ++++++++++++ tests/006_test_premium.py | 6 ++++++ 3 files changed, 55 insertions(+) diff --git a/tests/004_test_balance.py b/tests/004_test_balance.py index 6d48f23..5201db6 100644 --- a/tests/004_test_balance.py +++ b/tests/004_test_balance.py @@ -4,11 +4,17 @@ from contextlib import contextmanager from unittest.mock import AsyncMock, MagicMock, patch import pytest +from tonutils.exceptions import ProviderResponseError from pyfragment.types import TransactionError, WalletError from pyfragment.utils.wallet import process_transaction from tests.shared import VALID_SEED + +def _provider_error(code: int, message: str = "error") -> ProviderResponseError: + return ProviderResponseError(code=code, message=message, endpoint="api.tonapi.io") + + TRANSACTION_DATA = { "transaction": { "messages": [ @@ -94,3 +100,34 @@ async def test_one_nanoton_below_minimum_raises() -> None: async def test_invalid_payload_raises() -> None: with pytest.raises(TransactionError): await process_transaction(_make_client(), {"transaction": {}}) + + +@pytest.mark.asyncio +async def test_balance_check_failed_raises_wallet_error() -> None: + wallet = _make_wallet(balance_nanotons=1_000_000_000) + wallet.refresh = AsyncMock(side_effect=RuntimeError("network timeout")) + with _patch_wallet(wallet): + with pytest.raises(WalletError, match="balance"): + await process_transaction(_make_client(), TRANSACTION_DATA) + wallet.transfer.assert_not_called() + + +@pytest.mark.asyncio +async def test_rate_limit_retries_and_succeeds() -> None: + wallet = _make_wallet(balance_nanotons=1_000_000_000) + wallet.transfer = AsyncMock(side_effect=[_provider_error(429, "rate limited"), MagicMock(normalized_hash="abc123")]) + with _patch_wallet(wallet), patch("pyfragment.utils.wallet.clean_decode", return_value=""): + result = await process_transaction(_make_client(), TRANSACTION_DATA) + assert result == "abc123" + assert wallet.transfer.call_count == 2 + + +@pytest.mark.asyncio +async def test_duplicate_seqno_raises_after_retries() -> None: + wallet = _make_wallet(balance_nanotons=1_000_000_000) + err = _provider_error(406, "Duplicate msg_seqno") + wallet.transfer = AsyncMock(side_effect=[err, err, err]) + with _patch_wallet(wallet), patch("pyfragment.utils.wallet.clean_decode", return_value=""): + with pytest.raises(TransactionError, match="seqno"): + await process_transaction(_make_client(), TRANSACTION_DATA) + assert wallet.transfer.call_count == 3 diff --git a/tests/005_test_stars.py b/tests/005_test_stars.py index 826dc04..c3a38b8 100644 --- a/tests/005_test_stars.py +++ b/tests/005_test_stars.py @@ -90,6 +90,18 @@ async def test_giveaway_stars_amount_too_high(client: FragmentClient) -> None: await client.giveaway_stars("@channel", winners=1, amount=1_000_001) +@pytest.mark.asyncio +async def test_giveaway_stars_float_winners(client: FragmentClient) -> None: + with pytest.raises(ConfigurationError): + await client.giveaway_stars("@channel", winners=1.5, amount=500) # type: ignore[arg-type] + + +@pytest.mark.asyncio +async def test_giveaway_stars_float_amount(client: FragmentClient) -> None: + with pytest.raises(ConfigurationError): + await client.giveaway_stars("@channel", winners=1, amount=500.5) # type: ignore[arg-type] + + # Stars giveaway mocked tests diff --git a/tests/006_test_premium.py b/tests/006_test_premium.py index 1495474..6e06a22 100644 --- a/tests/006_test_premium.py +++ b/tests/006_test_premium.py @@ -78,6 +78,12 @@ async def test_giveaway_premium_winners_too_high(client: FragmentClient) -> None await client.giveaway_premium("@channel", winners=24_001, months=3) +@pytest.mark.asyncio +async def test_giveaway_premium_float_winners(client: FragmentClient) -> None: + with pytest.raises(ConfigurationError): + await client.giveaway_premium("@channel", winners=2.5, months=3) # type: ignore[arg-type] + + @pytest.mark.asyncio async def test_giveaway_premium_invalid_months(client: FragmentClient) -> None: with pytest.raises(ConfigurationError):