Files
aitbc/cli/tests/test_dual_mode_wallet.py
oib 15427c96c0 chore: update file permissions to executable across repository
- Change file mode from 644 to 755 for all project files
- Add chain_id parameter to get_balance RPC endpoint with default "ait-devnet"
- Rename Miner.extra_meta_data to extra_metadata for consistency
2026-03-06 22:17:54 +01:00

425 lines
15 KiB
Python
Executable File

"""Dual-Mode Wallet Tests
Tests for the dual-mode wallet adapter that supports both file-based
and daemon-based wallet operations.
"""
import pytest
import tempfile
import shutil
import json
from pathlib import Path
from unittest.mock import patch, MagicMock, Mock
from click.testing import CliRunner
from aitbc_cli.config import Config
from aitbc_cli.dual_mode_wallet_adapter import DualModeWalletAdapter
from aitbc_cli.wallet_daemon_client import WalletDaemonClient, WalletInfo, WalletBalance
from aitbc_cli.commands.wallet import wallet
from aitbc_cli.wallet_migration_service import WalletMigrationService
class TestWalletDaemonClient:
"""Test the wallet daemon client"""
def setup_method(self):
"""Set up test configuration"""
self.config = Config()
self.config.wallet_url = "http://localhost:8002"
self.client = WalletDaemonClient(self.config)
def test_client_initialization(self):
"""Test client initialization"""
assert self.client.base_url == "http://localhost:8002"
assert self.client.timeout == 30
@patch('aitbc_cli.wallet_daemon_client.httpx.Client')
def test_is_available_success(self, mock_client):
"""Test daemon availability check - success"""
mock_response = Mock()
mock_response.status_code = 200
mock_client.return_value.__enter__.return_value.get.return_value = mock_response
assert self.client.is_available() is True
@patch('aitbc_cli.wallet_daemon_client.httpx.Client')
def test_is_available_failure(self, mock_client):
"""Test daemon availability check - failure"""
mock_client.return_value.__enter__.side_effect = Exception("Connection failed")
assert self.client.is_available() is False
@patch('aitbc_cli.wallet_daemon_client.httpx.Client')
def test_create_wallet_success(self, mock_client):
"""Test wallet creation - success"""
mock_response = Mock()
mock_response.status_code = 201
mock_response.json.return_value = {
"wallet_id": "test-wallet",
"public_key": "0x123456",
"address": "aitbc1test",
"created_at": "2023-01-01T00:00:00Z",
"metadata": {}
}
mock_client.return_value.__enter__.return_value.post.return_value = mock_response
result = self.client.create_wallet("test-wallet", "password123")
assert isinstance(result, WalletInfo)
assert result.wallet_id == "test-wallet"
assert result.public_key == "0x123456"
assert result.address == "aitbc1test"
@patch('aitbc_cli.wallet_daemon_client.httpx.Client')
def test_list_wallets_success(self, mock_client):
"""Test wallet listing - success"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"wallets": [
{
"wallet_id": "wallet1",
"public_key": "0x111",
"address": "aitbc1wallet1",
"created_at": "2023-01-01T00:00:00Z"
},
{
"wallet_id": "wallet2",
"public_key": "0x222",
"address": "aitbc1wallet2",
"created_at": "2023-01-02T00:00:00Z"
}
]
}
mock_client.return_value.__enter__.return_value.get.return_value = mock_response
result = self.client.list_wallets()
assert len(result) == 2
assert result[0].wallet_id == "wallet1"
assert result[1].wallet_id == "wallet2"
@patch('aitbc_cli.wallet_daemon_client.httpx.Client')
def test_get_wallet_balance_success(self, mock_client):
"""Test wallet balance retrieval - success"""
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
"wallet_id": "test-wallet",
"balance": 100.5,
"address": "aitbc1test",
"last_updated": "2023-01-01T00:00:00Z"
}
mock_client.return_value.__enter__.return_value.get.return_value = mock_response
result = self.client.get_wallet_balance("test-wallet")
assert isinstance(result, WalletBalance)
assert result.wallet_id == "test-wallet"
assert result.balance == 100.5
class TestDualModeWalletAdapter:
"""Test the dual-mode wallet adapter"""
def setup_method(self):
"""Set up test environment"""
self.temp_dir = Path(tempfile.mkdtemp())
self.config = Config()
self.config.config_dir = self.temp_dir
# Mock wallet directory
self.wallet_dir = self.temp_dir / "wallets"
self.wallet_dir.mkdir(parents=True, exist_ok=True)
def teardown_method(self):
"""Clean up test environment"""
shutil.rmtree(self.temp_dir)
def test_file_mode_initialization(self):
"""Test adapter initialization in file mode"""
adapter = DualModeWalletAdapter(self.config, use_daemon=False)
assert adapter.use_daemon is False
assert adapter.daemon_client is None
assert adapter.wallet_dir == Path.home() / ".aitbc" / "wallets"
def test_daemon_mode_initialization(self):
"""Test adapter initialization in daemon mode"""
adapter = DualModeWalletAdapter(self.config, use_daemon=True)
assert adapter.use_daemon is True
assert adapter.daemon_client is not None
assert isinstance(adapter.daemon_client, WalletDaemonClient)
def test_create_wallet_file_mode(self):
"""Test wallet creation in file mode"""
adapter = DualModeWalletAdapter(self.config, use_daemon=False)
with patch('aitbc_cli.dual_mode_wallet_adapter.Path.home') as mock_home:
mock_home.return_value = self.temp_dir
adapter.wallet_dir = self.temp_dir / "wallets"
result = adapter.create_wallet("test-wallet", "password123", "hd")
assert result["mode"] == "file"
assert result["wallet_name"] == "test-wallet"
assert result["wallet_type"] == "hd"
# Check wallet file was created
wallet_file = self.wallet_dir / "test-wallet.json"
assert wallet_file.exists()
@patch('aitbc_cli.dual_mode_wallet_adapter.Path.home')
def test_create_wallet_daemon_mode_success(self, mock_home):
"""Test wallet creation in daemon mode - success"""
mock_home.return_value = self.temp_dir
adapter = DualModeWalletAdapter(self.config, use_daemon=True)
# Mock daemon client
mock_client = Mock()
mock_client.is_available.return_value = True
mock_client.create_wallet.return_value = WalletInfo(
wallet_id="test-wallet",
public_key="0x123456",
address="aitbc1test",
created_at="2023-01-01T00:00:00Z"
)
adapter.daemon_client = mock_client
result = adapter.create_wallet("test-wallet", "password123", metadata={})
assert result["mode"] == "daemon"
assert result["wallet_name"] == "test-wallet"
assert result["wallet_id"] == "test-wallet"
mock_client.create_wallet.assert_called_once()
@patch('aitbc_cli.dual_mode_wallet_adapter.Path.home')
def test_create_wallet_daemon_mode_fallback(self, mock_home):
"""Test wallet creation in daemon mode - fallback to file"""
mock_home.return_value = self.temp_dir
adapter = DualModeWalletAdapter(self.config, use_daemon=True)
# Mock unavailable daemon
mock_client = Mock()
mock_client.is_available.return_value = False
adapter.daemon_client = mock_client
result = adapter.create_wallet("test-wallet", "password123", "hd")
assert result["mode"] == "file"
assert result["wallet_name"] == "test-wallet"
@patch('aitbc_cli.dual_mode_wallet_adapter.Path.home')
def test_list_wallets_file_mode(self, mock_home):
"""Test wallet listing in file mode"""
mock_home.return_value = self.temp_dir
# Create test wallets
wallet1_data = {
"name": "wallet1",
"address": "aitbc1wallet1",
"balance": 10.0,
"wallet_type": "hd",
"created_at": "2023-01-01T00:00:00Z"
}
wallet2_data = {
"name": "wallet2",
"address": "aitbc1wallet2",
"balance": 20.0,
"wallet_type": "simple",
"created_at": "2023-01-02T00:00:00Z"
}
with open(self.wallet_dir / "wallet1.json", "w") as f:
json.dump(wallet1_data, f)
with open(self.wallet_dir / "wallet2.json", "w") as f:
json.dump(wallet2_data, f)
adapter = DualModeWalletAdapter(self.config, use_daemon=False)
adapter.wallet_dir = self.wallet_dir
result = adapter.list_wallets()
assert len(result) == 2
assert result[0]["wallet_name"] == "wallet1"
assert result[0]["mode"] == "file"
assert result[1]["wallet_name"] == "wallet2"
assert result[1]["mode"] == "file"
class TestWalletCommands:
"""Test wallet commands with dual-mode support"""
def setup_method(self):
"""Set up test environment"""
self.runner = CliRunner()
self.temp_dir = Path(tempfile.mkdtemp())
def teardown_method(self):
"""Clean up test environment"""
shutil.rmtree(self.temp_dir)
@patch('aitbc_cli.commands.wallet.Path.home')
def test_wallet_create_file_mode(self, mock_home):
"""Test wallet creation command in file mode"""
mock_home.return_value = self.temp_dir
result = self.runner.invoke(wallet, [
'create', 'test-wallet', '--type', 'simple', '--no-encrypt'
])
assert result.exit_code == 0
assert 'Created file wallet' in result.output
@patch('aitbc_cli.commands.wallet.Path.home')
def test_wallet_create_daemon_mode_unavailable(self, mock_home):
"""Test wallet creation command in daemon mode when daemon unavailable"""
mock_home.return_value = self.temp_dir
result = self.runner.invoke(wallet, [
'--use-daemon', 'create', 'test-wallet', '--type', 'simple', '--no-encrypt'
])
assert result.exit_code == 0
assert 'Falling back to file-based wallet' in result.output
@patch('aitbc_cli.commands.wallet.Path.home')
def test_wallet_list_file_mode(self, mock_home):
"""Test wallet listing command in file mode"""
mock_home.return_value = self.temp_dir
# Create a test wallet first
wallet_dir = self.temp_dir / ".aitbc" / "wallets"
wallet_dir.mkdir(parents=True, exist_ok=True)
wallet_data = {
"name": "test-wallet",
"address": "aitbc1test",
"balance": 10.0,
"wallet_type": "hd",
"created_at": "2023-01-01T00:00:00Z"
}
with open(wallet_dir / "test-wallet.json", "w") as f:
json.dump(wallet_data, f)
result = self.runner.invoke(wallet, ['list'])
assert result.exit_code == 0
assert 'test-wallet' in result.output
@patch('aitbc_cli.commands.wallet.Path.home')
def test_wallet_balance_file_mode(self, mock_home):
"""Test wallet balance command in file mode"""
mock_home.return_value = self.temp_dir
# Create a test wallet first
wallet_dir = self.temp_dir / ".aitbc" / "wallets"
wallet_dir.mkdir(parents=True, exist_ok=True)
wallet_data = {
"name": "test-wallet",
"address": "aitbc1test",
"balance": 25.5,
"wallet_type": "hd",
"created_at": "2023-01-01T00:00:00Z"
}
with open(wallet_dir / "test-wallet.json", "w") as f:
json.dump(wallet_data, f)
result = self.runner.invoke(wallet, ['balance'])
assert result.exit_code == 0
assert '25.5' in result.output
class TestWalletMigrationService:
"""Test wallet migration service"""
def setup_method(self):
"""Set up test environment"""
self.temp_dir = Path(tempfile.mkdtemp())
self.config = Config()
self.config.config_dir = self.temp_dir
# Mock wallet directory
self.wallet_dir = self.temp_dir / "wallets"
self.wallet_dir.mkdir(parents=True, exist_ok=True)
def teardown_method(self):
"""Clean up test environment"""
shutil.rmtree(self.temp_dir)
@patch('aitbc_cli.wallet_migration_service.Path.home')
def test_migration_status_daemon_unavailable(self, mock_home):
"""Test migration status when daemon is unavailable"""
mock_home.return_value = self.temp_dir
migration_service = WalletMigrationService(self.config)
# Create test file wallet
wallet_data = {
"name": "test-wallet",
"address": "aitbc1test",
"balance": 10.0,
"wallet_type": "hd",
"created_at": "2023-01-01T00:00:00Z"
}
with open(self.wallet_dir / "test-wallet.json", "w") as f:
json.dump(wallet_data, f)
status = migration_service.get_migration_status()
assert status["daemon_available"] is False
assert status["total_file_wallets"] == 1
assert status["total_daemon_wallets"] == 0
assert "test-wallet" in status["file_only_wallets"]
@patch('aitbc_cli.wallet_migration_service.Path.home')
def test_migrate_to_daemon_success(self, mock_home):
"""Test migration to daemon - success"""
mock_home.return_value = self.temp_dir
migration_service = WalletMigrationService(self.config)
# Create test file wallet
wallet_data = {
"name": "test-wallet",
"address": "aitbc1test",
"balance": 10.0,
"wallet_type": "hd",
"created_at": "2023-01-01T00:00:00Z",
"transactions": []
}
with open(self.wallet_dir / "test-wallet.json", "w") as f:
json.dump(wallet_data, f)
# Mock successful daemon migration
mock_adapter = Mock()
mock_adapter.is_daemon_available.return_value = True
mock_adapter.get_wallet_info.return_value = None # Wallet doesn't exist in daemon
mock_adapter.create_wallet.return_value = {
"wallet_id": "test-wallet",
"public_key": "0x123456",
"address": "aitbc1test"
}
migration_service.daemon_adapter = mock_adapter
result = migration_service.migrate_to_daemon("test-wallet", "password123")
assert result["wallet_name"] == "test-wallet"
assert result["source_mode"] == "file"
assert result["target_mode"] == "daemon"
assert result["original_balance"] == 10.0
if __name__ == "__main__":
pytest.main([__file__])