Files

426 lines
14 KiB
Python
Raw Permalink Normal View History

"""Tests for the profiler data models."""
from datetime import UTC, datetime, timedelta
from decimal import Decimal
import pytest
from polymarket_insider_tracker.profiler.models import Transaction, WalletInfo, WalletProfile
class TestTransaction:
"""Tests for the Transaction dataclass."""
@pytest.fixture
def sample_transaction(self) -> Transaction:
"""Create a sample transaction."""
return Transaction(
hash="0xabc123",
block_number=50000000,
timestamp=datetime(2026, 1, 4, 12, 0, 0, tzinfo=UTC),
from_address="0xsender",
to_address="0xreceiver",
value=Decimal("1000000000000000000"), # 1 MATIC
gas_used=21000,
gas_price=Decimal("50000000000"), # 50 Gwei
)
def test_transaction_creation(self, sample_transaction: Transaction) -> None:
"""Test creating a transaction."""
assert sample_transaction.hash == "0xabc123"
assert sample_transaction.block_number == 50000000
assert sample_transaction.from_address == "0xsender"
assert sample_transaction.to_address == "0xreceiver"
def test_value_matic(self, sample_transaction: Transaction) -> None:
"""Test value in MATIC conversion."""
assert sample_transaction.value_matic == Decimal("1")
def test_value_matic_fractional(self) -> None:
"""Test fractional MATIC value."""
tx = Transaction(
hash="0x123",
block_number=1,
timestamp=datetime.now(UTC),
from_address="0x1",
to_address="0x2",
value=Decimal("500000000000000000"), # 0.5 MATIC
gas_used=21000,
gas_price=Decimal("50000000000"),
)
assert tx.value_matic == Decimal("0.5")
def test_gas_cost_wei(self, sample_transaction: Transaction) -> None:
"""Test gas cost in Wei."""
expected = 21000 * 50000000000
assert sample_transaction.gas_cost_wei == Decimal(expected)
def test_gas_cost_matic(self, sample_transaction: Transaction) -> None:
"""Test gas cost in MATIC."""
# 21000 * 50 Gwei = 1050000 Gwei = 0.00105 MATIC
expected = Decimal("21000") * Decimal("50000000000") / Decimal("1000000000000000000")
assert sample_transaction.gas_cost_matic == expected
def test_transaction_frozen(self, sample_transaction: Transaction) -> None:
"""Test that transaction is immutable."""
with pytest.raises(AttributeError):
sample_transaction.hash = "0xnew" # type: ignore[misc]
def test_transaction_no_recipient(self) -> None:
"""Test transaction with no recipient (contract creation)."""
tx = Transaction(
hash="0x123",
block_number=1,
timestamp=datetime.now(UTC),
from_address="0x1",
to_address=None,
value=Decimal("0"),
gas_used=100000,
gas_price=Decimal("50000000000"),
)
assert tx.to_address is None
class TestWalletInfo:
"""Tests for the WalletInfo dataclass."""
@pytest.fixture
def sample_wallet(self) -> WalletInfo:
"""Create a sample wallet info."""
return WalletInfo(
address="0xwallet123",
transaction_count=100,
balance_wei=Decimal("5000000000000000000"), # 5 MATIC
first_transaction=None,
)
@pytest.fixture
def wallet_with_transaction(self) -> WalletInfo:
"""Create a wallet with first transaction."""
first_tx = Transaction(
hash="0xfirst",
block_number=1000000,
timestamp=datetime.now(UTC) - timedelta(days=365),
from_address="0xfaucet",
to_address="0xwallet123",
value=Decimal("1000000000000000000"),
gas_used=21000,
gas_price=Decimal("50000000000"),
)
return WalletInfo(
address="0xwallet123",
transaction_count=100,
balance_wei=Decimal("5000000000000000000"),
first_transaction=first_tx,
)
def test_wallet_creation(self, sample_wallet: WalletInfo) -> None:
"""Test creating wallet info."""
assert sample_wallet.address == "0xwallet123"
assert sample_wallet.transaction_count == 100
assert sample_wallet.balance_wei == Decimal("5000000000000000000")
def test_balance_matic(self, sample_wallet: WalletInfo) -> None:
"""Test balance in MATIC conversion."""
assert sample_wallet.balance_matic == Decimal("5")
def test_is_fresh_false(self, sample_wallet: WalletInfo) -> None:
"""Test that wallet with many transactions is not fresh."""
assert sample_wallet.is_fresh is False
def test_is_fresh_true(self) -> None:
"""Test that wallet with few transactions is fresh."""
wallet = WalletInfo(
address="0xnewwallet",
transaction_count=5,
balance_wei=Decimal("1000000000000000000"),
)
assert wallet.is_fresh is True
def test_is_fresh_boundary(self) -> None:
"""Test fresh wallet boundary (10 transactions)."""
wallet_9 = WalletInfo(
address="0x1",
transaction_count=9,
balance_wei=Decimal("0"),
)
wallet_10 = WalletInfo(
address="0x2",
transaction_count=10,
balance_wei=Decimal("0"),
)
assert wallet_9.is_fresh is True
assert wallet_10.is_fresh is False
def test_wallet_age_days_no_transaction(self, sample_wallet: WalletInfo) -> None:
"""Test wallet age when no first transaction."""
assert sample_wallet.wallet_age_days is None
def test_wallet_age_days_with_transaction(self, wallet_with_transaction: WalletInfo) -> None:
"""Test wallet age calculation."""
age = wallet_with_transaction.wallet_age_days
assert age is not None
# Should be approximately 365 days
assert 364 < age < 366
def test_wallet_frozen(self, sample_wallet: WalletInfo) -> None:
"""Test that wallet info is immutable."""
with pytest.raises(AttributeError):
sample_wallet.address = "0xnew" # type: ignore[misc]
def test_wallet_zero_balance(self) -> None:
"""Test wallet with zero balance."""
wallet = WalletInfo(
address="0xempty",
transaction_count=0,
balance_wei=Decimal("0"),
)
assert wallet.balance_matic == Decimal("0")
assert wallet.is_fresh is True
def test_wallet_very_small_balance(self) -> None:
"""Test wallet with very small balance."""
wallet = WalletInfo(
address="0xdust",
transaction_count=1,
balance_wei=Decimal("1"), # 1 Wei
)
# Should be a very small fraction
expected = Decimal("1") / Decimal("1000000000000000000")
assert wallet.balance_matic == expected
class TestTransactionEquality:
"""Tests for transaction equality and hashing."""
def test_equal_transactions(self) -> None:
"""Test that identical transactions are equal."""
tx1 = Transaction(
hash="0xabc",
block_number=1,
timestamp=datetime(2026, 1, 1, tzinfo=UTC),
from_address="0x1",
to_address="0x2",
value=Decimal("1000"),
gas_used=21000,
gas_price=Decimal("50"),
)
tx2 = Transaction(
hash="0xabc",
block_number=1,
timestamp=datetime(2026, 1, 1, tzinfo=UTC),
from_address="0x1",
to_address="0x2",
value=Decimal("1000"),
gas_used=21000,
gas_price=Decimal("50"),
)
assert tx1 == tx2
def test_different_transactions(self) -> None:
"""Test that different transactions are not equal."""
tx1 = Transaction(
hash="0xabc",
block_number=1,
timestamp=datetime(2026, 1, 1, tzinfo=UTC),
from_address="0x1",
to_address="0x2",
value=Decimal("1000"),
gas_used=21000,
gas_price=Decimal("50"),
)
tx2 = Transaction(
hash="0xdef", # Different hash
block_number=1,
timestamp=datetime(2026, 1, 1, tzinfo=UTC),
from_address="0x1",
to_address="0x2",
value=Decimal("1000"),
gas_used=21000,
gas_price=Decimal("50"),
)
assert tx1 != tx2
def test_transaction_hashable(self) -> None:
"""Test that transactions can be used in sets."""
tx = Transaction(
hash="0xabc",
block_number=1,
timestamp=datetime(2026, 1, 1, tzinfo=UTC),
from_address="0x1",
to_address="0x2",
value=Decimal("1000"),
gas_used=21000,
gas_price=Decimal("50"),
)
tx_set = {tx}
assert tx in tx_set
class TestWalletProfile:
"""Tests for the WalletProfile dataclass."""
@pytest.fixture
def fresh_profile(self) -> WalletProfile:
"""Create a fresh wallet profile."""
return WalletProfile(
address="0xfresh",
nonce=2,
first_seen=datetime.now(UTC) - timedelta(hours=6),
age_hours=6.0,
is_fresh=True,
total_tx_count=2,
matic_balance=Decimal("1000000000000000000"), # 1 MATIC
usdc_balance=Decimal("1000000"), # 1 USDC
fresh_threshold=5,
)
@pytest.fixture
def old_profile(self) -> WalletProfile:
"""Create an old wallet profile."""
return WalletProfile(
address="0xold",
nonce=500,
first_seen=datetime.now(UTC) - timedelta(days=365),
age_hours=365 * 24,
is_fresh=False,
total_tx_count=500,
matic_balance=Decimal("100000000000000000000"), # 100 MATIC
usdc_balance=Decimal("10000000000"), # 10000 USDC
fresh_threshold=5,
)
def test_profile_creation(self, fresh_profile: WalletProfile) -> None:
"""Test creating a wallet profile."""
assert fresh_profile.address == "0xfresh"
assert fresh_profile.nonce == 2
assert fresh_profile.is_fresh is True
assert fresh_profile.age_hours == 6.0
def test_age_days(self, fresh_profile: WalletProfile) -> None:
"""Test age_days property."""
assert fresh_profile.age_days == 0.25 # 6 hours = 0.25 days
def test_age_days_none(self) -> None:
"""Test age_days when age_hours is None."""
profile = WalletProfile(
address="0xnew",
nonce=0,
first_seen=None,
age_hours=None,
is_fresh=True,
total_tx_count=0,
matic_balance=Decimal("0"),
usdc_balance=Decimal("0"),
)
assert profile.age_days is None
def test_matic_balance_formatted(self, fresh_profile: WalletProfile) -> None:
"""Test MATIC balance formatting."""
assert fresh_profile.matic_balance_formatted == Decimal("1")
def test_usdc_balance_formatted(self, fresh_profile: WalletProfile) -> None:
"""Test USDC balance formatting."""
assert fresh_profile.usdc_balance_formatted == Decimal("1")
def test_is_brand_new(self) -> None:
"""Test is_brand_new property."""
brand_new = WalletProfile(
address="0xnew",
nonce=0,
first_seen=None,
age_hours=None,
is_fresh=True,
total_tx_count=0,
matic_balance=Decimal("0"),
usdc_balance=Decimal("0"),
)
assert brand_new.is_brand_new is True
not_brand_new = WalletProfile(
address="0xold",
nonce=1,
first_seen=datetime.now(UTC),
age_hours=1.0,
is_fresh=True,
total_tx_count=1,
matic_balance=Decimal("0"),
usdc_balance=Decimal("0"),
)
assert not_brand_new.is_brand_new is False
def test_freshness_score_brand_new(self) -> None:
"""Test freshness score for brand new wallet."""
profile = WalletProfile(
address="0xnew",
nonce=0,
first_seen=None,
age_hours=None,
is_fresh=True,
total_tx_count=0,
matic_balance=Decimal("0"),
usdc_balance=Decimal("0"),
fresh_threshold=5,
)
# nonce_score = 1.0 (0/5 = 0, 1-0 = 1)
# age_score = 1.0 (None = assumed new)
# score = 0.6 * 1.0 + 0.4 * 1.0 = 1.0
assert profile.freshness_score == 1.0
def test_freshness_score_old_wallet(self, old_profile: WalletProfile) -> None:
"""Test freshness score for old wallet."""
# nonce_score = max(0, 1 - 500/5) = 0
# age_score = max(0, 1 - 8760/48) = 0
# score = 0
assert old_profile.freshness_score == 0.0
def test_freshness_score_moderate(self) -> None:
"""Test freshness score for moderately fresh wallet."""
profile = WalletProfile(
address="0xmoderate",
nonce=2,
first_seen=datetime.now(UTC) - timedelta(hours=24),
age_hours=24.0,
is_fresh=True,
total_tx_count=2,
matic_balance=Decimal("0"),
usdc_balance=Decimal("0"),
fresh_threshold=5,
)
# nonce_score = 1 - 2/5 = 0.6
# age_score = 1 - 24/48 = 0.5
# score = 0.6 * 0.6 + 0.4 * 0.5 = 0.36 + 0.2 = 0.56
assert profile.freshness_score == pytest.approx(0.56)
def test_profile_frozen(self, fresh_profile: WalletProfile) -> None:
"""Test that wallet profile is immutable."""
with pytest.raises(AttributeError):
fresh_profile.nonce = 100 # type: ignore[misc]
def test_analyzed_at_default(self) -> None:
"""Test that analyzed_at has a default."""
before = datetime.now(UTC)
profile = WalletProfile(
address="0x1",
nonce=0,
first_seen=None,
age_hours=None,
is_fresh=True,
total_tx_count=0,
matic_balance=Decimal("0"),
usdc_balance=Decimal("0"),
)
after = datetime.now(UTC)
assert before <= profile.analyzed_at <= after