Skip to content

Commit 1b0eecb

Browse files
committed
Feature: proper unit test case for voucher
1 parent d542076 commit 1b0eecb

File tree

3 files changed

+144
-6
lines changed

3 files changed

+144
-6
lines changed

tests/unit/conftest.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
from datetime import datetime, timezone
1414
from pathlib import Path
1515
from tempfile import NamedTemporaryFile
16-
from unittest.mock import AsyncMock, patch
16+
from unittest.mock import AsyncMock, MagicMock, patch
1717

1818
import pytest
1919
from aleph.sdk.chains.common import generate_key
2020
from aleph.sdk.chains.ethereum import ETHAccount, get_fallback_private_key
21-
from aleph.sdk.types import StoredContent
21+
from aleph.sdk.types import StoredContent, Voucher, VoucherAttribute
2222
from aleph_message.models import Chain, ItemHash, ItemType, StoreContent, StoreMessage
2323
from aleph_message.models.base import MessageType
2424

@@ -37,6 +37,12 @@
3737
FAKE_STORE_HASH,
3838
)
3939

40+
# Constants for voucher testing
41+
MOCK_ADDRESS = "0x1234567890123456789012345678901234567890"
42+
MOCK_SOLANA_ADDRESS = "abcdefghijklmnopqrstuvwxyz123456789"
43+
MOCK_METADATA_ID = "metadata123"
44+
MOCK_VOUCHER_ID = "voucher123"
45+
4046

4147
@pytest.fixture
4248
def new_config_file() -> Generator[Path, None, None]:
@@ -466,3 +472,55 @@ async def mock_json():
466472
instance.get = AsyncMock(return_value=mock_response)
467473

468474
yield mock_session
475+
476+
477+
@pytest.fixture
478+
def mock_vouchers():
479+
"""Create mock vouchers for testing."""
480+
# Create EVM voucher
481+
evm_voucher = Voucher(
482+
id=MOCK_VOUCHER_ID,
483+
metadata_id=MOCK_METADATA_ID,
484+
name="EVM Test Voucher",
485+
description="A test voucher for EVM chains",
486+
external_url="https://example.com",
487+
image="https://example.com/image.png",
488+
icon="https://example.com/icon.png",
489+
attributes=[
490+
VoucherAttribute(trait_type="Duration", value="30 days", display_type="string"),
491+
VoucherAttribute(trait_type="Compute Units", value="4", display_type="number"),
492+
VoucherAttribute(trait_type="Type", value="instance", display_type="string"),
493+
],
494+
)
495+
496+
# Create Solana voucher
497+
solana_voucher = Voucher(
498+
id="solticket123",
499+
metadata_id=MOCK_METADATA_ID,
500+
name="Solana Test Voucher",
501+
description="A test voucher for Solana",
502+
external_url="https://example.com",
503+
image="https://example.com/image.png",
504+
icon="https://example.com/icon.png",
505+
attributes=[
506+
VoucherAttribute(trait_type="Duration", value="60 days", display_type="string"),
507+
VoucherAttribute(trait_type="Compute Units", value="8", display_type="number"),
508+
VoucherAttribute(trait_type="Type", value="instance", display_type="string"),
509+
],
510+
)
511+
512+
return evm_voucher, solana_voucher
513+
514+
515+
@pytest.fixture
516+
def mock_voucher_service(mock_vouchers):
517+
"""Create a mock voucher service with pre-configured responses."""
518+
evm_voucher, solana_voucher = mock_vouchers
519+
520+
mock_service = MagicMock()
521+
mock_service.fetch_vouchers_by_chain = AsyncMock(return_value=[evm_voucher])
522+
mock_service.get_vouchers = AsyncMock(return_value=[evm_voucher, solana_voucher])
523+
mock_service.get_evm_vouchers = AsyncMock(return_value=[evm_voucher])
524+
mock_service.get_solana_vouchers = AsyncMock(return_value=[solana_voucher])
525+
526+
return mock_service

tests/unit/test_commands.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def test_account_sign_bytes(env_files):
244244
assert result.stdout.startswith("\nSignature:")
245245

246246

247-
def test_account_balance(mocker, env_files):
247+
def test_account_balance(mocker, env_files, mock_voucher_service):
248248
settings.CONFIG_FILE = env_files[1]
249249
balance_response = {
250250
"address": "0xCAfEcAfeCAfECaFeCaFecaFecaFECafECafeCaFe",
@@ -255,14 +255,19 @@ def test_account_balance(mocker, env_files):
255255
}
256256

257257
mocker.patch("aleph_client.commands.account.get_balance", return_value=balance_response)
258-
mocker.patch("aleph_client.voucher.VoucherManager.get_all", return_value=[])
258+
# TODO: used the mocked_client fixture instead (also need to move get_balance to the SDK)
259+
mock_client = mocker.AsyncMock()
260+
mock_client.voucher = mock_voucher_service
261+
mocker.patch("aleph_client.commands.account.AuthenticatedAlephHttpClient.__aenter__", return_value=mock_client)
259262

260263
result = runner.invoke(
261264
app, ["account", "balance", "--address", "0xCAfEcAfeCAfECaFeCaFecaFecaFECafECafeCaFe", "--chain", "ETH"]
262265
)
263266
assert result.exit_code == 0
264267
assert result.stdout.startswith("╭─ Account Infos")
265268
assert "Available: 20189.67" in result.stdout
269+
assert "Vouchers:" in result.stdout
270+
assert "EVM Test Voucher" in result.stdout
266271

267272

268273
def test_account_balance_error(mocker, env_files):

tests/unit/test_instance.py

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
)
4848
from aleph_client.utils import FORBIDDEN_HOSTS, sanitize_url
4949

50+
from .conftest import MOCK_METADATA_ID, MOCK_VOUCHER_ID
5051
from .mocks import (
5152
FAKE_ADDRESS_EVM,
5253
FAKE_CRN_BASIC_HASH,
@@ -254,7 +255,7 @@ def create_mock_shutil():
254255
return MagicMock(which=MagicMock(return_value="/root/.cargo/bin/sevctl", move=MagicMock(return_value="/fake/path")))
255256

256257

257-
def create_mock_client(mock_crn_list, payment_type="superfluid"):
258+
def create_mock_client(mock_crn_list, payment_type="superfluid", mock_voucher_service=None):
258259
# Create a proper mock for the crn service
259260
mock_crn_service = MagicMock()
260261
mock_crn_service.get_crns_list = AsyncMock(return_value={"crns": mock_crn_list})
@@ -271,11 +272,13 @@ def create_mock_client(mock_crn_list, payment_type="superfluid"):
271272
)
272273
),
273274
)
274-
# Set the crn attribute to the properly mocked service
275+
# Set the service attributes
275276
mock_client.crn = mock_crn_service
277+
mock_client.voucher = mock_voucher_service
276278

277279
mock_client_class = MagicMock()
278280
mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client)
281+
279282
return mock_client_class, mock_client
280283

281284

@@ -291,6 +294,48 @@ def response_get_program_price(ptype):
291294
mock_crn_service = MagicMock()
292295
mock_crn_service.get_crns_list = AsyncMock(return_value={"crns": mock_crn_list or []})
293296

297+
# Create voucher attributes using the proper types
298+
from aleph.sdk.types import Voucher, VoucherAttribute
299+
300+
# Create EVM voucher
301+
evm_voucher = Voucher(
302+
id=MOCK_VOUCHER_ID,
303+
metadata_id=MOCK_METADATA_ID,
304+
name="EVM Test Voucher",
305+
description="A test voucher for EVM chains",
306+
external_url="https://example.com",
307+
image="https://example.com/image.png",
308+
icon="https://example.com/icon.png",
309+
attributes=[
310+
VoucherAttribute(trait_type="Duration", value="30 days", display_type="string"),
311+
VoucherAttribute(trait_type="Compute Units", value="4", display_type="number"),
312+
VoucherAttribute(trait_type="Type", value="instance", display_type="string"),
313+
],
314+
)
315+
316+
# Create Solana voucher
317+
solana_voucher = Voucher(
318+
id="solticket123",
319+
metadata_id=MOCK_METADATA_ID,
320+
name="Solana Test Voucher",
321+
description="A test voucher for Solana",
322+
external_url="https://example.com",
323+
image="https://example.com/image.png",
324+
icon="https://example.com/icon.png",
325+
attributes=[
326+
VoucherAttribute(trait_type="Duration", value="60 days", display_type="string"),
327+
VoucherAttribute(trait_type="Compute Units", value="8", display_type="number"),
328+
VoucherAttribute(trait_type="Type", value="instance", display_type="string"),
329+
],
330+
)
331+
332+
# Create a proper mock for voucher service
333+
mock_voucher_service = MagicMock()
334+
mock_voucher_service.fetch_vouchers_by_chain = AsyncMock(return_value=[evm_voucher])
335+
mock_voucher_service.get_vouchers = AsyncMock(return_value=[evm_voucher, solana_voucher])
336+
mock_voucher_service.get_evm_vouchers = AsyncMock(return_value=[evm_voucher])
337+
mock_voucher_service.get_solana_vouchers = AsyncMock(return_value=[solana_voucher])
338+
294339
mock_response_get_message = create_mock_instance_message(mock_account, payg=True)
295340
mock_response_create_instance = MagicMock(item_hash=FAKE_VM_HASH)
296341

@@ -312,6 +357,7 @@ def response_get_program_price(ptype):
312357
# Set the service attributes
313358
mock_auth_client.crn = mock_crn_service
314359
mock_auth_client.port_forwarder = mock_port_forwarder
360+
mock_auth_client.voucher = mock_voucher_service
315361

316362
if payment_types:
317363
mock_auth_client.get_program_price = AsyncMock(
@@ -374,6 +420,9 @@ def create_mock_vm_coco_client():
374420
"coco_hold_evm",
375421
"coco_superfluid_evm",
376422
"gpu_superfluid_evm",
423+
"nft_payment_avax",
424+
"nft_payment_base",
425+
"nft_payment_sol",
377426
],
378427
argnames="args, expected",
379428
argvalues=[
@@ -445,6 +494,32 @@ def create_mock_vm_coco_client():
445494
},
446495
(FAKE_VM_HASH, FAKE_CRN_GPU_URL, "BASE"),
447496
),
497+
( # nft_payment_avax
498+
{
499+
"payment_type": "nft",
500+
"payment_chain": "AVAX",
501+
"rootfs": "debian12",
502+
},
503+
(FAKE_VM_HASH, None, "AVAX"),
504+
),
505+
( # nft_payment_base
506+
{
507+
"payment_type": "nft",
508+
"payment_chain": "BASE",
509+
"rootfs": "debian12",
510+
"crn_url": FAKE_CRN_BASIC_URL,
511+
},
512+
(FAKE_VM_HASH, None, "BASE"),
513+
),
514+
( # nft_payment_sol
515+
{
516+
"payment_type": "nft",
517+
"payment_chain": "SOL",
518+
"rootfs": "debian12",
519+
"crn_url": FAKE_CRN_BASIC_URL,
520+
},
521+
(FAKE_VM_HASH, None, "SOL"),
522+
),
448523
],
449524
)
450525
@pytest.mark.asyncio

0 commit comments

Comments
 (0)