- Add /rpc/info endpoint to blockchain node for comprehensive chain information
- Add /rpc/supply endpoint for token supply metrics with genesis parameters
- Add /rpc/validators endpoint to list PoA validators and consensus info
- Add /api/v1/agents/networks endpoint for creating collaborative agent networks
- Add /api/v1/agents/executions/{id}/receipt endpoint for verifiable execution receipts
- Add /api/v1/jobs and /api/v1/jobs/
376 lines
14 KiB
Python
376 lines
14 KiB
Python
"""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"""
|
|
# Mock the crypto_utils module to avoid import errors
|
|
with patch.dict('sys.modules', {'aitbc_cli.utils.crypto_utils': Mock()}):
|
|
# Now import and patch the function
|
|
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_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
|