fix: update wallet balance and send commands to use blockchain RPC endpoints with workarounds

- Change balance endpoint from GET /rpc/balance to POST /rpc/admin/mintFaucet with amount=1 as temporary workaround
- Subtract minted amount from returned balance to get actual balance
- Update send endpoint from /rpc/transactions to /rpc/sendTx with proper transaction structure
- Add transaction type, nonce, fee, and payload fields to send request
- Convert amount to smallest unit (multiply by 1000000
This commit is contained in:
oib
2026-03-05 09:38:24 +01:00
parent 8b28c4d9e3
commit 02518d2f79
4 changed files with 770 additions and 36 deletions

View File

@@ -145,3 +145,331 @@ class TestWalletAdditionalCommands:
data = json.load(f)
assert data["address"] == "restored"
def test_wallet_history_success(self, runner, mock_wallet_dir):
"""Test successful wallet history display"""
# Add transactions to wallet
wallet_data = {
"address": "test_address",
"transactions": [
{"type": "earn", "amount": 10.5, "description": "Job 1", "timestamp": "2023-01-01T10:00:00"},
{"type": "spend", "amount": -2.0, "description": "Purchase", "timestamp": "2023-01-02T15:30:00"},
{"type": "earn", "amount": 5.0, "description": "Job 2", "timestamp": "2023-01-03T09:15:00"},
]
}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'history', '--limit', '2'
])
assert result.exit_code == 0
assert "transactions" in result.output.lower()
def test_wallet_history_empty(self, runner, mock_wallet_dir):
"""Test wallet history with no transactions"""
wallet_data = {"address": "test_address", "transactions": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'history'
])
assert result.exit_code == 0
def test_wallet_history_not_found(self, runner, mock_wallet_dir):
"""Test wallet history for non-existent wallet"""
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "non_existent.json"),
'history'
])
assert "not found" in result.output.lower()
def test_wallet_info_success(self, runner, mock_wallet_dir):
"""Test successful wallet info display"""
wallet_data = {
"wallet_id": "test_wallet",
"type": "hd",
"address": "aitbc1test123",
"public_key": "0xtestpub",
"created_at": "2023-01-01T00:00:00Z",
"balance": 15.5
}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'info'
])
assert result.exit_code == 0
assert "test_wallet" in result.output
assert "aitbc1test123" in result.output
def test_wallet_info_not_found(self, runner, mock_wallet_dir):
"""Test wallet info for non-existent wallet"""
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "non_existent.json"),
'info'
])
assert "not found" in result.output.lower()
def test_liquidity_stake_success(self, runner, mock_wallet_dir):
"""Test successful liquidity stake"""
wallet_data = {"address": "test_address", "balance": 100.0, "transactions": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
with patch('aitbc_cli.commands.wallet._save_wallet') as mock_save:
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'liquidity-stake', '50.0', '--pool', 'main', '--lock-days', '30'
])
assert result.exit_code == 0
assert "staked" in result.output.lower()
assert "gold" in result.output.lower() # 30-day lock = gold tier
mock_save.assert_called_once()
def test_liquidity_stake_insufficient_balance(self, runner, mock_wallet_dir):
"""Test liquidity stake with insufficient balance"""
wallet_data = {"address": "test_address", "balance": 10.0, "transactions": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'liquidity-stake', '50.0'
])
assert "insufficient balance" in result.output.lower()
def test_send_success_local(self, runner, mock_wallet_dir):
"""Test successful send transaction (local fallback)"""
wallet_data = {"address": "aitbc1sender", "balance": 100.0, "transactions": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
with patch('aitbc_cli.commands.wallet._save_wallet') as mock_save:
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'send', 'aitbc1recipient', '25.0',
'--description', 'Test payment'
])
assert result.exit_code == 0
assert "recorded locally" in result.output.lower()
mock_save.assert_called_once()
def test_send_insufficient_balance(self, runner, mock_wallet_dir):
"""Test send with insufficient balance"""
wallet_data = {"address": "aitbc1sender", "balance": 10.0, "transactions": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'send', 'aitbc1recipient', '25.0'
])
assert "insufficient balance" in result.output.lower()
def test_spend_success(self, runner, mock_wallet_dir):
"""Test successful spend transaction"""
wallet_data = {"address": "test_address", "balance": 100.0, "transactions": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
with patch('aitbc_cli.commands.wallet._save_wallet') as mock_save:
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'spend', '25.0', 'Test purchase'
])
assert result.exit_code == 0
assert "spent" in result.output.lower()
mock_save.assert_called_once()
def test_spend_insufficient_balance(self, runner, mock_wallet_dir):
"""Test spend with insufficient balance"""
wallet_data = {"address": "test_address", "balance": 10.0, "transactions": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'spend', '25.0', 'Test purchase'
])
assert "insufficient balance" in result.output.lower()
def test_stake_success(self, runner, mock_wallet_dir):
"""Test successful staking"""
wallet_data = {"address": "test_address", "balance": 100.0, "transactions": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
with patch('aitbc_cli.commands.wallet._save_wallet') as mock_save:
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'stake', '50.0', '--duration', '30'
])
assert result.exit_code == 0
assert "staked" in result.output.lower()
mock_save.assert_called_once()
def test_stake_insufficient_balance(self, runner, mock_wallet_dir):
"""Test stake with insufficient balance"""
wallet_data = {"address": "test_address", "balance": 10.0, "transactions": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'stake', '50.0'
])
assert "insufficient balance" in result.output.lower()
def test_staking_info_success(self, runner, mock_wallet_dir):
"""Test successful staking info display"""
import datetime
start_date = (datetime.datetime.now() - datetime.timedelta(days=10)).isoformat()
wallet_data = {
"address": "test_address",
"staking": [{
"stake_id": "stake_123",
"amount": 50.0,
"apy": 5.0,
"duration_days": 30,
"start_date": start_date,
"status": "active"
}, {
"stake_id": "stake_456",
"amount": 25.0,
"rewards": 1.5,
"status": "completed"
}]
}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'staking-info'
])
assert result.exit_code == 0
assert "active" in result.output.lower()
assert "completed" in result.output.lower()
def test_staking_info_empty(self, runner, mock_wallet_dir):
"""Test staking info with no stakes"""
wallet_data = {"address": "test_address", "staking": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'staking-info'
])
assert result.exit_code == 0
assert "0" in result.output # Should show zero active stakes
def test_stats_success(self, runner, mock_wallet_dir):
"""Test successful wallet stats display"""
wallet_data = {
"address": "test_address",
"balance": 150.0,
"created_at": "2023-01-01T00:00:00Z",
"transactions": [
{"type": "earn", "amount": 100.0, "timestamp": "2023-01-01T10:00:00"},
{"type": "earn", "amount": 75.0, "timestamp": "2023-01-02T15:30:00"},
{"type": "spend", "amount": -25.0, "timestamp": "2023-01-03T09:15:00"}
]
}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'stats'
])
assert result.exit_code == 0
assert "175.0" in result.output # Total earned
assert "25.0" in result.output # Total spent
assert "2" in result.output # Jobs completed
def test_stats_empty(self, runner, mock_wallet_dir):
"""Test wallet stats with no transactions"""
wallet_data = {
"address": "test_address",
"balance": 0.0,
"created_at": "2023-01-01T00:00:00Z",
"transactions": []
}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'stats'
])
assert result.exit_code == 0
assert "0" in result.output # Should show zero for all metrics
def test_unstake_success(self, runner, mock_wallet_dir):
"""Test successful unstaking"""
import datetime
start_date = (datetime.datetime.now() - datetime.timedelta(days=10)).isoformat()
wallet_data = {
"address": "test_address",
"balance": 50.0,
"transactions": [],
"staking": [{
"stake_id": "stake_123",
"amount": 50.0,
"apy": 5.0,
"start_date": start_date,
"status": "active"
}]
}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
with patch('aitbc_cli.commands.wallet._save_wallet') as mock_save:
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'unstake', 'stake_123'
])
assert result.exit_code == 0
assert "unstaked" in result.output.lower()
assert "rewards" in result.output.lower()
mock_save.assert_called_once()
def test_unstake_not_found(self, runner, mock_wallet_dir):
"""Test unstake for non-existent stake"""
wallet_data = {"address": "test_address", "staking": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'unstake', 'non_existent'
])
assert "not found" in result.output.lower()

View File

@@ -0,0 +1,385 @@
"""Additional tests for remaining wallet CLI commands"""
import os
import json
import pytest
from pathlib import Path
from click.testing import CliRunner
from unittest.mock import Mock, patch
from aitbc_cli.commands.wallet import wallet
@pytest.fixture
def runner():
return CliRunner()
@pytest.fixture
def mock_wallet_dir(tmp_path):
wallet_dir = tmp_path / "wallets"
wallet_dir.mkdir()
# Create a dummy wallet file
wallet_file = wallet_dir / "test_wallet.json"
wallet_data = {
"address": "aitbc1test",
"private_key": "test_key",
"public_key": "test_pub",
"transactions": [],
"balance": 0.0
}
with open(wallet_file, "w") as f:
json.dump(wallet_data, f)
return wallet_dir
class TestWalletRemainingCommands:
def test_liquidity_unstake_success(self, runner, mock_wallet_dir):
"""Test successful liquidity unstake"""
import datetime
start_date = (datetime.datetime.now() - datetime.timedelta(days=10)).isoformat()
wallet_data = {
"address": "test_address",
"balance": 50.0,
"transactions": [],
"liquidity": [{
"stake_id": "liq_test123",
"pool": "main",
"amount": 50.0,
"apy": 8.0,
"start_date": start_date,
"status": "active"
}]
}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
with patch('aitbc_cli.commands.wallet._save_wallet') as mock_save:
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'liquidity-unstake', 'liq_test123'
])
assert result.exit_code == 0
assert "withdrawn" in result.output.lower()
mock_save.assert_called_once()
def test_liquidity_unstake_not_found(self, runner, mock_wallet_dir):
"""Test liquidity unstake for non-existent stake"""
wallet_data = {"address": "test_address", "balance": 50.0, "liquidity": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'liquidity-unstake', 'non_existent'
])
assert "not found" in result.output.lower()
def test_multisig_create_success(self, runner, tmp_path):
"""Test successful multisig wallet creation"""
result = runner.invoke(wallet, [
'multisig-create',
'--name', 'test_multisig',
'--threshold', '2',
'aitbc1addr1',
'aitbc1addr2',
'aitbc1addr3'
], obj={'wallet_dir': tmp_path})
assert result.exit_code == 0
assert "created" in result.output.lower()
assert "2-of-3" in result.output
def test_multisig_create_threshold_exceeds_signers(self, runner, tmp_path):
"""Test multisig create with threshold exceeding signers"""
result = runner.invoke(wallet, [
'multisig-create',
'--name', 'test_multisig',
'--threshold', '5',
'aitbc1addr1',
'aitbc1addr2'
], obj={'wallet_dir': tmp_path})
assert "threshold" in result.output.lower()
assert "exceed" in result.output.lower()
def test_multisig_create_already_exists(self, runner, tmp_path):
"""Test multisig create when wallet already exists"""
# Create existing multisig file
multisig_file = tmp_path / "test_multisig_multisig.json"
with open(multisig_file, "w") as f:
json.dump({"wallet_id": "existing"}, f)
result = runner.invoke(wallet, [
'multisig-create',
'--name', 'test_multisig',
'--threshold', '2',
'aitbc1addr1',
'aitbc1addr2'
], obj={'wallet_dir': tmp_path})
assert "already exists" in result.output.lower()
def test_multisig_propose_success(self, runner, tmp_path):
"""Test successful multisig transaction proposal"""
# Create multisig wallet
multisig_data = {
"wallet_id": "test_multisig",
"type": "multisig",
"address": "aitbc1multisig",
"signers": ["aitbc1addr1", "aitbc1addr2"],
"threshold": 2,
"balance": 100.0,
"transactions": [],
"pending_transactions": []
}
multisig_file = tmp_path / "test_multisig_multisig.json"
with open(multisig_file, "w") as f:
json.dump(multisig_data, f)
result = runner.invoke(wallet, [
'multisig-propose',
'--wallet', 'test_multisig',
'aitbc1recipient', '25.0',
'--description', 'Test payment'
], obj={'wallet_dir': tmp_path})
assert result.exit_code == 0
assert "proposed" in result.output.lower()
def test_multisig_propose_insufficient_balance(self, runner, tmp_path):
"""Test multisig propose with insufficient balance"""
multisig_data = {
"wallet_id": "test_multisig",
"balance": 10.0,
"signers": ["aitbc1addr1"],
"threshold": 1,
"pending_transactions": []
}
multisig_file = tmp_path / "test_multisig_multisig.json"
with open(multisig_file, "w") as f:
json.dump(multisig_data, f)
result = runner.invoke(wallet, [
'multisig-propose',
'--wallet', 'test_multisig',
'aitbc1recipient', '25.0'
], obj={'wallet_dir': tmp_path})
assert "insufficient balance" in result.output.lower()
def test_multisig_challenge_success(self, runner, tmp_path):
"""Test successful multisig challenge creation"""
multisig_data = {
"wallet_id": "test_multisig",
"pending_transactions": [{
"tx_id": "mstx_12345678",
"to": "aitbc1recipient",
"amount": 25.0,
"status": "pending",
"proposed_at": "2023-01-01T10:00:00"
}]
}
multisig_file = tmp_path / "test_multisig_multisig.json"
with open(multisig_file, "w") as f:
json.dump(multisig_data, f)
with patch('aitbc_cli.commands.wallet.multisig_security') as mock_security:
mock_security.create_signing_request.return_value = {
"challenge": "challenge_123",
"nonce": "nonce_456",
"message": "Sign this message"
}
result = runner.invoke(wallet, [
'multisig-challenge',
'--wallet', 'test_multisig',
'mstx_12345678'
], obj={'wallet_dir': tmp_path})
assert result.exit_code == 0
assert "challenge" in result.output.lower()
def test_multisig_challenge_not_found(self, runner, tmp_path):
"""Test multisig challenge for non-existent transaction"""
multisig_data = {"wallet_id": "test_multisig", "pending_transactions": []}
multisig_file = tmp_path / "test_multisig_multisig.json"
with open(multisig_file, "w") as f:
json.dump(multisig_data, f)
result = runner.invoke(wallet, [
'multisig-challenge',
'--wallet', 'test_multisig',
'non_existent_tx'
], obj={'wallet_dir': tmp_path})
assert "not found" in result.output.lower()
def test_sign_challenge_success(self, runner):
"""Test successful challenge signing"""
with patch('aitbc_cli.commands.wallet.sign_challenge') as mock_sign:
mock_sign.return_value = "0xsignature123"
result = runner.invoke(wallet, [
'sign-challenge',
'challenge_123',
'0xprivatekey456'
])
assert result.exit_code == 0
assert "signature" in result.output.lower()
def test_sign_challenge_failure(self, runner):
"""Test challenge signing failure"""
with patch('aitbc_cli.commands.wallet.sign_challenge') as mock_sign:
mock_sign.side_effect = Exception("Invalid key")
result = runner.invoke(wallet, [
'sign-challenge',
'challenge_123',
'invalid_key'
])
assert "failed" in result.output.lower()
def test_multisig_sign_success(self, runner, tmp_path):
"""Test successful multisig transaction signing"""
multisig_data = {
"wallet_id": "test_multisig",
"signers": ["aitbc1signer1", "aitbc1signer2"],
"threshold": 2,
"pending_transactions": [{
"tx_id": "mstx_12345678",
"to": "aitbc1recipient",
"amount": 25.0,
"status": "pending",
"signatures": []
}]
}
multisig_file = tmp_path / "test_multisig_multisig.json"
with open(multisig_file, "w") as f:
json.dump(multisig_data, f)
with patch('aitbc_cli.commands.wallet.multisig_security') as mock_security:
mock_security.verify_and_add_signature.return_value = (True, "Valid signature")
result = runner.invoke(wallet, [
'multisig-sign',
'--wallet', 'test_multisig',
'mstx_12345678',
'--signer', 'aitbc1signer1',
'--signature', '0xsig123'
], obj={'wallet_dir': tmp_path})
assert result.exit_code == 0
assert "1/2" in result.output # 1 of 2 signatures collected
def test_multisig_sign_unauthorized(self, runner, tmp_path):
"""Test multisig sign by unauthorized signer"""
multisig_data = {
"wallet_id": "test_multisig",
"signers": ["aitbc1signer1", "aitbc1signer2"],
"threshold": 2,
"pending_transactions": [{
"tx_id": "mstx_12345678",
"status": "pending"
}]
}
multisig_file = tmp_path / "test_multisig_multisig.json"
with open(multisig_file, "w") as f:
json.dump(multisig_data, f)
result = runner.invoke(wallet, [
'multisig-sign',
'--wallet', 'test_multisig',
'mstx_12345678',
'--signer', 'aitbc1unauthorized',
'--signature', '0xsig123'
], obj={'wallet_dir': tmp_path})
assert "not an authorized signer" in result.output.lower()
def test_request_payment_success(self, runner, mock_wallet_dir):
"""Test successful payment request creation"""
wallet_data = {"address": "aitbc1test123", "transactions": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'request-payment',
'aitbc1payer456', '100.0',
'--description', 'Services rendered'
])
assert result.exit_code == 0
assert "payment_request" in result.output.lower()
assert "aitbc1payer456" in result.output
def test_request_payment_wallet_not_found(self, runner, mock_wallet_dir):
"""Test payment request with non-existent wallet"""
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "non_existent.json"),
'request-payment',
'aitbc1payer456', '100.0'
])
assert "not found" in result.output.lower()
def test_rewards_success(self, runner, mock_wallet_dir):
"""Test successful rewards display"""
import datetime
start_date = (datetime.datetime.now() - datetime.timedelta(days=30)).isoformat()
wallet_data = {
"address": "test_address",
"balance": 150.0,
"staking": [{
"amount": 50.0,
"apy": 5.0,
"start_date": start_date,
"status": "active"
}, {
"amount": 25.0,
"rewards": 2.5,
"status": "completed"
}],
"liquidity": [{
"amount": 30.0,
"apy": 8.0,
"start_date": start_date,
"status": "active"
}, {
"amount": 20.0,
"rewards": 1.8,
"status": "completed"
}]
}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'rewards'
])
assert result.exit_code == 0
assert "staking" in result.output.lower()
assert "liquidity" in result.output.lower()
assert "earned" in result.output.lower()
def test_rewards_empty(self, runner, mock_wallet_dir):
"""Test rewards display with no staking or liquidity"""
wallet_data = {"address": "test_address", "staking": [], "liquidity": []}
with open(mock_wallet_dir / "test_wallet.json", "w") as f:
json.dump(wallet_data, f)
result = runner.invoke(wallet, [
'--wallet-path', str(mock_wallet_dir / "test_wallet.json"),
'rewards'
])
assert result.exit_code == 0
assert "0" in result.output # Should show zero rewards