Skip to content

Commit 5f93d05

Browse files
committed
fix: unit test & use get_balances from alephHttpClient instead of hardcoded one
1 parent 36cc337 commit 5f93d05

File tree

10 files changed

+129
-85
lines changed

10 files changed

+129
-85
lines changed

src/aleph_client/commands/account.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import aiohttp
1111
import typer
12-
from aleph.sdk import AuthenticatedAlephHttpClient, AlephHttpClient
12+
from aleph.sdk import AlephHttpClient, AuthenticatedAlephHttpClient
1313
from aleph.sdk.account import _load_account
1414
from aleph.sdk.chains.common import generate_key
1515
from aleph.sdk.chains.solana import parse_private_key as parse_solana_private_key
@@ -303,14 +303,16 @@ async def balance(
303303

304304
if address:
305305
try:
306-
balance_data = await get_balance(address)
306+
async with AlephHttpClient() as client:
307+
balance_data = await client.get_balances(address)
308+
available = balance_data.balance - balance_data.locked_amount
307309
infos = [
308-
Text.from_markup(f"Address: [bright_cyan]{balance_data['address']}[/bright_cyan]"),
310+
Text.from_markup(f"Address: [bright_cyan]{balance_data.address}[/bright_cyan]"),
309311
Text.from_markup(
310-
f"\nBalance: [bright_cyan]{displayable_amount(balance_data['balance'], decimals=2)}[/bright_cyan]"
312+
f"\nBalance: [bright_cyan]{displayable_amount(balance_data.balance, decimals=2)}[/bright_cyan]"
311313
),
312314
]
313-
details = balance_data.get("details")
315+
details = balance_data.details
314316
if details:
315317
infos += [Text("\n ↳ Details")]
316318
for chain_, chain_balance in details.items():
@@ -319,32 +321,25 @@ async def balance(
319321
f"\n {chain_}: [orange3]{displayable_amount(chain_balance, decimals=2)}[/orange3]"
320322
)
321323
]
322-
available_color = "bright_cyan" if balance_data["available_amount"] >= 0 else "red"
324+
available_color = "bright_cyan" if available >= 0 else "red"
323325
infos += [
324326
Text.from_markup(
325-
f"\n - Locked: [bright_cyan]{displayable_amount(balance_data['locked_amount'], decimals=2)}"
327+
f"\n - Locked: [bright_cyan]{displayable_amount(balance_data.locked_amount, decimals=2)}"
326328
"[/bright_cyan]"
327329
),
328330
Text.from_markup(
329331
f"\n - Available: [{available_color}]"
330-
f"{displayable_amount(balance_data['available_amount'], decimals=2)}"
332+
f"{displayable_amount(available, decimals=2)}"
331333
f"[/{available_color}]"
332334
),
333335
]
334336

335-
try:
336-
# Fetch user Credits
337-
async with AlephHttpClient() as client:
338-
credits_balance = await client.get_balance(address).credit_balance
339-
infos += [
340-
Text("\nCredits:"),
341-
Text.from_markup(
342-
f"[bright_cyan] {displayable_amount(credits_balance, decimals=2)}[/bright_cyan]"
343-
),
344-
]
345-
except Exception as e:
346-
# In the case we call on ccn that does not support credits yet
347-
logger.warning(f"Failed to fetch credits balance: {e}")
337+
infos += [
338+
Text("\nCredits:"),
339+
Text.from_markup(
340+
f"[bright_cyan] {displayable_amount(balance_data.credit_balance, decimals=2)}[/bright_cyan]"
341+
),
342+
]
348343

349344
# Get vouchers and add them to Account Info panel
350345
async with AuthenticatedAlephHttpClient(account=account) as client:

src/aleph_client/commands/credit.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ async def show(
4848

4949
if address:
5050
async with AlephHttpClient(api_server=settings.API_HOST) as client:
51-
credit = await client.get_balance(address=address)
51+
credit = await client.get_balances(address=address)
5252
if json:
5353
typer.echo(credit.model_dump_json(indent=4))
5454
else:
@@ -107,7 +107,7 @@ async def history(
107107
table.add_column("Origin Ref")
108108
table.add_column("Expiration Date")
109109

110-
for credit in filtered_credits.credit_balances:
110+
for credit in filtered_credits.credit_history:
111111
timestamp = Text(credit.message_timestamp.strftime("%Y-%m-%d %H:%M:%S"))
112112
amount = Text(displayable_amount(credit.amount, decimals=2), style="cyan")
113113
payment_method = Text(credit.payment_method if credit.payment_method else "-")

src/aleph_client/commands/instance/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
from rich.text import Text
6464

6565
from aleph_client.commands import help_strings
66-
from aleph_client.commands.account import get_balance
6766
from aleph_client.commands.instance.display import CRNTable, show_instances
6867
from aleph_client.commands.instance.network import (
6968
call_program_crn_list,
@@ -419,7 +418,9 @@ async def create(
419418
compute_unit_price = pricing.data[pricing_entity].price.get("compute_unit")
420419
if payment_type in [PaymentType.hold, PaymentType.superfluid]:
421420
# Early check with minimal cost (Gas + Aleph ERC20)
422-
available_funds = Decimal(0 if is_stream else (await get_balance(address))["available_amount"])
421+
balance_response = await client.get_balances(address)
422+
available_amount = balance_response.balance - balance_response.locked_amount
423+
available_funds = Decimal(0 if is_stream else available_amount)
423424
try:
424425
# Get compute_unit price from PricingPerEntity
425426
if is_stream and isinstance(account, ETHAccount):
@@ -451,7 +452,7 @@ async def create(
451452
if payment_type == PaymentType.credit:
452453
async with AlephHttpClient(api_server=settings.API_HOST) as client:
453454
try:
454-
credit_info = await client.get_balance(address=address)
455+
credit_info = await client.get_balances(address=address)
455456
if isinstance(compute_unit_price, Price) and compute_unit_price.credit:
456457
credit_price = Decimal(str(compute_unit_price.credit)) * tier.compute_units
457458
if credit_info.credit_balance < credit_price:

src/aleph_client/commands/program.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
from rich.text import Text
4747

4848
from aleph_client.commands import help_strings
49-
from aleph_client.commands.account import get_balance
5049
from aleph_client.commands.pricing import PricingEntity, fetch_pricing_aggregate
5150
from aleph_client.commands.utils import (
5251
display_mounted_volumes,
@@ -248,7 +247,7 @@ async def upload(
248247
typer.echo(f"Failed to estimate program cost, error: {e}")
249248
raise typer.Exit(code=1) from e
250249

251-
available_funds = Decimal((await get_balance(address))["available_amount"])
250+
available_funds = Decimal((await client.get_balances(address))["available_amount"])
252251
try:
253252
if available_funds < required_tokens:
254253
raise InsufficientFundsError(TokenType.ALEPH, float(required_tokens), float(available_funds))

tests/unit/conftest.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import time
1212
from collections.abc import Generator
1313
from datetime import datetime, timezone
14+
from decimal import Decimal
1415
from pathlib import Path
1516
from tempfile import NamedTemporaryFile
1617
from unittest.mock import AsyncMock, MagicMock, patch
@@ -20,6 +21,7 @@
2021
from aleph.sdk.chains.ethereum import ETHAccount, get_fallback_private_key
2122
from aleph.sdk.client.services.crn import CrnList
2223
from aleph.sdk.client.services.pricing import PricingModel
24+
from aleph.sdk.query.responses import BalanceResponse
2325
from aleph.sdk.types import StoredContent, Voucher, VoucherAttribute
2426
from aleph_message.models import Chain, ItemHash, ItemType, StoreContent, StoreMessage
2527
from aleph_message.models.base import MessageType
@@ -530,3 +532,28 @@ def mock_voucher_service(mock_vouchers):
530532
mock_service.get_solana_vouchers = AsyncMock(return_value=[solana_voucher])
531533

532534
return mock_service
535+
536+
537+
@pytest.fixture
538+
def mock_voucher_empty():
539+
"""Create a mock voucher service with pre-configured responses."""
540+
mock_service = MagicMock()
541+
mock_service.fetch_vouchers_by_chain = AsyncMock(return_value=[])
542+
mock_service.get_vouchers = AsyncMock(return_value=[])
543+
mock_service.get_evm_vouchers = AsyncMock(return_value=[])
544+
mock_service.get_solana_vouchers = AsyncMock(return_value=[])
545+
546+
return mock_service
547+
548+
549+
@pytest.fixture
550+
def mock_get_balances():
551+
# Create a proper BalanceResponse with all Decimal values
552+
response = BalanceResponse(
553+
address="0xCAfEcAfeCAfECaFeCaFecaFecaFECafECafeCaFe",
554+
balance=Decimal(24853),
555+
details={"AVAX": Decimal(4000), "BASE": Decimal(10000), "ETH": Decimal(10853)},
556+
locked_amount=Decimal("4663.334518051392"),
557+
credit_balance=5000,
558+
)
559+
return response

tests/unit/test_commands.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from aleph_client.commands.files import upload
1717

1818
from .mocks import FAKE_STORE_HASH, FAKE_STORE_HASH_PUBLISHER
19+
from .test_instance import create_mock_client
1920

2021
runner = CliRunner()
2122

@@ -244,41 +245,49 @@ def test_account_sign_bytes(env_files):
244245
assert result.stdout.startswith("\nSignature:")
245246

246247

247-
def test_account_balance(mocker, env_files, mock_voucher_service):
248+
def test_account_balance(mocker, env_files, mock_voucher_service, mock_get_balances):
249+
"""
250+
This test verifies that the account balance command correctly displays balance and voucher information.
251+
"""
252+
248253
settings.CONFIG_FILE = env_files[1]
249-
balance_response = {
250-
"address": "0xCAfEcAfeCAfECaFeCaFecaFecaFECafECafeCaFe",
251-
"balance": 24853,
252-
"details": {"AVAX": 4000, "BASE": 10000, "ETH": 10853},
253-
"locked_amount": 4663.334518051392,
254-
"available_amount": 20189.665481948608,
255-
}
254+
mock_client_class, mock_client = create_mock_client(None, None, mock_get_balances=mock_get_balances)
256255

257-
mocker.patch("aleph_client.commands.account.get_balance", return_value=balance_response)
258-
# TODO: used the mocked_client fixture instead (also need to move get_balance to the SDK)
259-
mock_client = mocker.AsyncMock()
260256
mock_client.voucher = mock_voucher_service
261-
mocker.patch("aleph_client.commands.account.AuthenticatedAlephHttpClient.__aenter__", return_value=mock_client)
257+
258+
# Replace both client types with our mock implementation
259+
mocker.patch("aleph_client.commands.account.AlephHttpClient", mock_client_class)
260+
mocker.patch("aleph_client.commands.account.AuthenticatedAlephHttpClient", mock_client_class)
262261

263262
result = runner.invoke(
264263
app, ["account", "balance", "--address", "0xCAfEcAfeCAfECaFeCaFecaFecaFECafECafeCaFe", "--chain", "ETH"]
265264
)
265+
266266
assert result.exit_code == 0
267267
assert result.stdout.startswith("╭─ Account Infos")
268268
assert "Available: 20189.67" in result.stdout
269269
assert "Vouchers:" in result.stdout
270270
assert "EVM Test Voucher" in result.stdout
271271

272272

273-
def test_account_balance_error(mocker, env_files):
273+
def test_account_balance_error(mocker, env_files, mock_voucher_empty):
274274
"""Test error handling in the account balance command when API returns an error."""
275275
settings.CONFIG_FILE = env_files[1]
276276

277-
# Mock get_balance to raise an exception - simulating a non-200 response
278-
mock_get_balance = mocker.patch(
279-
"aleph_client.commands.account.get_balance",
280-
side_effect=Exception("Failed to retrieve balance for address test. Status code: 404"),
277+
mock_client_class = MagicMock()
278+
mock_client = MagicMock()
279+
mock_client.__aenter__.return_value = mock_client
280+
mock_client.__aexit__.return_value = None
281+
mock_client.get_balances = AsyncMock(
282+
side_effect=Exception(
283+
"Failed to retrieve balance for address 0xCAfEcAfeCAfECaFeCaFecaFecaFECafECafeCaFe. Status code: 404"
284+
)
281285
)
286+
mock_client.voucher = mock_voucher_empty
287+
mock_client_class.return_value = mock_client
288+
289+
mocker.patch("aleph_client.commands.account.AlephHttpClient", mock_client_class)
290+
mocker.patch("aleph_client.commands.account.AuthenticatedAlephHttpClient", mock_client_class)
282291

283292
# Test with an address directly
284293
result = runner.invoke(
@@ -288,7 +297,6 @@ def test_account_balance_error(mocker, env_files):
288297
# The command should run without crashing but report the error
289298
assert result.exit_code == 0
290299
assert "Failed to retrieve balance for address" in result.stdout
291-
mock_get_balance.assert_called_once()
292300

293301

294302
def test_account_vouchers_display(mocker, env_files, mock_voucher_service):

tests/unit/test_credits.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ def mock_credit_balance_response():
1717
mock_response.json = AsyncMock(
1818
return_value={
1919
"address": "0x1234567890123456789012345678901234567890",
20-
"credits": 1000000000,
20+
"balance": 24853,
21+
"locked_amount": 4663.33,
22+
"credit_balance": 1000000000,
23+
"details": {"AVAX": 4000, "BASE": 10000, "ETH": 10853},
2124
}
2225
)
2326
return mock_response
@@ -32,7 +35,7 @@ def mock_credit_history_response():
3235
mock_response.json = AsyncMock(
3336
return_value={
3437
"address": "0x1234567890123456789012345678901234567890",
35-
"credit_balances": [
38+
"credit_history": [
3639
{
3740
"amount": 1000000000,
3841
"message_timestamp": "2023-06-15T12:30:45Z",
@@ -124,10 +127,9 @@ async def run(mock_get):
124127

125128
# Try to parse the output as JSON to validate it's properly formatted
126129
parsed_json = json.loads(captured.out)
127-
128130
# Verify expected data is in the parsed JSON
129131
assert parsed_json["address"] == "0x1234567890123456789012345678901234567890"
130-
assert parsed_json["credits"] == 1000000000
132+
assert parsed_json["credit_balance"] == 1000000000
131133

132134

133135
@pytest.mark.asyncio
@@ -259,9 +261,9 @@ async def run(mock_get):
259261

260262
# Verify expected data is in the parsed JSON
261263
assert parsed_json["address"] == "0x1234567890123456789012345678901234567890"
262-
assert parsed_json["credit_balances"][0]["amount"] == 1000000000
263-
assert parsed_json["credit_balances"][0]["payment_method"] == "credit_card"
264-
assert len(parsed_json["credit_balances"]) == 2
264+
assert parsed_json["credit_history"][0]["amount"] == 1000000000
265+
assert parsed_json["credit_history"][0]["payment_method"] == "credit_card"
266+
assert len(parsed_json["credit_history"]) == 2
265267

266268

267269
@pytest.mark.asyncio

0 commit comments

Comments
 (0)