- Update all agent command endpoints to remove /v1 prefix for API consistency - Rename `success` variable to `is_success` in chain.py to avoid conflict with success() function - Rename `output` parameter to `output_file` in genesis.py for clarity - Add admin command help tests to verify command structure - Update blockchain status endpoint from /status to /v1/health in tests - Mark admin help command as working
358 lines
12 KiB
Python
358 lines
12 KiB
Python
"""Tests for blockchain CLI commands"""
|
|
|
|
import pytest
|
|
import json
|
|
from click.testing import CliRunner
|
|
from unittest.mock import Mock, patch
|
|
from aitbc_cli.commands.blockchain import blockchain
|
|
|
|
|
|
@pytest.fixture
|
|
def runner():
|
|
"""Create CLI runner"""
|
|
return CliRunner()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_config():
|
|
"""Mock configuration"""
|
|
config = Mock()
|
|
config.coordinator_url = "http://test:8000"
|
|
config.api_key = "test_api_key"
|
|
return config
|
|
|
|
|
|
class TestBlockchainCommands:
|
|
"""Test blockchain command group"""
|
|
|
|
@patch('aitbc_cli.commands.blockchain.httpx.Client')
|
|
def test_blocks_success(self, mock_client_class, runner, mock_config):
|
|
"""Test successful block listing"""
|
|
# Setup mock
|
|
mock_client = Mock()
|
|
mock_client_class.return_value.__enter__.return_value = mock_client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"blocks": [
|
|
{"height": 100, "hash": "0xabc123", "timestamp": "2024-01-01T00:00:00"},
|
|
{"height": 99, "hash": "0xdef456", "timestamp": "2024-01-01T00:01:00"}
|
|
]
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
# Run command
|
|
result = runner.invoke(blockchain, [
|
|
'blocks',
|
|
'--limit', '2'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Assertions
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert len(data['blocks']) == 2
|
|
assert data['blocks'][0]['height'] == 100
|
|
|
|
# Verify API call
|
|
mock_client.get.assert_called_once_with(
|
|
'http://test:8000/v1/explorer/blocks',
|
|
params={"limit": 2},
|
|
headers={"X-Api-Key": "test_api_key"}
|
|
)
|
|
|
|
@patch('aitbc_cli.commands.blockchain.httpx.Client')
|
|
def test_block_details(self, mock_client_class, runner, mock_config):
|
|
"""Test getting block details"""
|
|
# Setup mock
|
|
mock_client = Mock()
|
|
mock_client_class.return_value.__enter__.return_value = mock_client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"height": 100,
|
|
"hash": "0xabc123",
|
|
"transactions": ["0xtx1", "0xtx2"],
|
|
"timestamp": "2024-01-01T00:00:00",
|
|
"validator": "validator1"
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
# Run command
|
|
result = runner.invoke(blockchain, [
|
|
'block',
|
|
'100'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Assertions
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert data['height'] == 100
|
|
assert data['hash'] == '0xabc123'
|
|
assert len(data['transactions']) == 2
|
|
|
|
# Verify API call
|
|
mock_client.get.assert_called_once_with(
|
|
'http://test:8000/v1/explorer/blocks/100',
|
|
headers={"X-Api-Key": "test_api_key"}
|
|
)
|
|
|
|
@patch('aitbc_cli.commands.blockchain.httpx.Client')
|
|
def test_transaction(self, mock_client_class, runner, mock_config):
|
|
"""Test getting transaction details"""
|
|
# Setup mock
|
|
mock_client = Mock()
|
|
mock_client_class.return_value.__enter__.return_value = mock_client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"hash": "0xtx123",
|
|
"block": 100,
|
|
"from": "0xabc",
|
|
"to": "0xdef",
|
|
"amount": "1000",
|
|
"fee": "10",
|
|
"status": "confirmed"
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
# Run command
|
|
result = runner.invoke(blockchain, [
|
|
'transaction',
|
|
'0xtx123'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Assertions
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert data['hash'] == '0xtx123'
|
|
assert data['block'] == 100
|
|
assert data['status'] == 'confirmed'
|
|
|
|
# Verify API call
|
|
mock_client.get.assert_called_once_with(
|
|
'http://test:8000/v1/explorer/transactions/0xtx123',
|
|
headers={"X-Api-Key": "test_api_key"}
|
|
)
|
|
|
|
@patch('aitbc_cli.commands.blockchain.httpx.Client')
|
|
def test_node_status(self, mock_client_class, runner, mock_config):
|
|
"""Test getting node status"""
|
|
# Setup mock
|
|
mock_client = Mock()
|
|
mock_client_class.return_value.__enter__.return_value = mock_client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"status": "running",
|
|
"version": "1.0.0",
|
|
"height": 1000,
|
|
"peers": 5,
|
|
"synced": True
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
# Run command
|
|
result = runner.invoke(blockchain, [
|
|
'status',
|
|
'--node', '1'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Assertions
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert data['node'] == 1
|
|
assert data['rpc_url'] == 'http://localhost:8082'
|
|
assert data['status']['status'] == 'running'
|
|
|
|
# Verify API call
|
|
mock_client.get.assert_called_once_with(
|
|
'http://localhost:8082/v1/health',
|
|
timeout=5
|
|
)
|
|
|
|
@patch('aitbc_cli.commands.blockchain.httpx.Client')
|
|
def test_sync_status(self, mock_client_class, runner, mock_config):
|
|
"""Test getting sync status"""
|
|
# Setup mock
|
|
mock_client = Mock()
|
|
mock_client_class.return_value.__enter__.return_value = mock_client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"synced": True,
|
|
"current_height": 1000,
|
|
"target_height": 1000,
|
|
"sync_percentage": 100.0
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
# Run command
|
|
result = runner.invoke(blockchain, [
|
|
'sync-status'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Assertions
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert data['synced'] == True
|
|
assert data['sync_percentage'] == 100.0
|
|
|
|
# Verify API call
|
|
mock_client.get.assert_called_once_with(
|
|
'http://test:8000/v1/blockchain/sync',
|
|
headers={"X-Api-Key": "test_api_key"}
|
|
)
|
|
|
|
@patch('aitbc_cli.commands.blockchain.httpx.Client')
|
|
def test_peers(self, mock_client_class, runner, mock_config):
|
|
"""Test listing peers"""
|
|
# Setup mock
|
|
mock_client = Mock()
|
|
mock_client_class.return_value.__enter__.return_value = mock_client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"peers": [
|
|
{"id": "peer1", "address": "1.2.3.4:8080", "connected": True},
|
|
{"id": "peer2", "address": "5.6.7.8:8080", "connected": False}
|
|
]
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
# Run command
|
|
result = runner.invoke(blockchain, [
|
|
'peers'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Assertions
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert len(data['peers']) == 2
|
|
assert data['peers'][0]['connected'] == True
|
|
|
|
# Verify API call
|
|
mock_client.get.assert_called_once_with(
|
|
'http://test:8000/v1/blockchain/peers',
|
|
headers={"X-Api-Key": "test_api_key"}
|
|
)
|
|
|
|
@patch('aitbc_cli.commands.blockchain.httpx.Client')
|
|
def test_info(self, mock_client_class, runner, mock_config):
|
|
"""Test getting blockchain info"""
|
|
# Setup mock
|
|
mock_client = Mock()
|
|
mock_client_class.return_value.__enter__.return_value = mock_client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"network": "aitbc-mainnet",
|
|
"chain_id": "aitbc-1",
|
|
"block_time": 5,
|
|
"min_stake": 1000,
|
|
"total_supply": "1000000000"
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
# Run command
|
|
result = runner.invoke(blockchain, [
|
|
'info'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Assertions
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert data['network'] == 'aitbc-mainnet'
|
|
assert data['block_time'] == 5
|
|
|
|
# Verify API call
|
|
mock_client.get.assert_called_once_with(
|
|
'http://test:8000/v1/blockchain/info',
|
|
headers={"X-Api-Key": "test_api_key"}
|
|
)
|
|
|
|
@patch('aitbc_cli.commands.blockchain.httpx.Client')
|
|
def test_supply(self, mock_client_class, runner, mock_config):
|
|
"""Test getting token supply"""
|
|
# Setup mock
|
|
mock_client = Mock()
|
|
mock_client_class.return_value.__enter__.return_value = mock_client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"total_supply": "1000000000",
|
|
"circulating_supply": "500000000",
|
|
"staked": "300000000",
|
|
"burned": "200000000"
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
# Run command
|
|
result = runner.invoke(blockchain, [
|
|
'supply'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Assertions
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert data['total_supply'] == '1000000000'
|
|
assert data['circulating_supply'] == '500000000'
|
|
|
|
# Verify API call
|
|
mock_client.get.assert_called_once_with(
|
|
'http://test:8000/v1/blockchain/supply',
|
|
headers={"X-Api-Key": "test_api_key"}
|
|
)
|
|
|
|
@patch('aitbc_cli.commands.blockchain.httpx.Client')
|
|
def test_validators(self, mock_client_class, runner, mock_config):
|
|
"""Test listing validators"""
|
|
# Setup mock
|
|
mock_client = Mock()
|
|
mock_client_class.return_value.__enter__.return_value = mock_client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {
|
|
"validators": [
|
|
{"address": "0xval1", "stake": "100000", "status": "active"},
|
|
{"address": "0xval2", "stake": "50000", "status": "active"}
|
|
]
|
|
}
|
|
mock_client.get.return_value = mock_response
|
|
|
|
# Run command
|
|
result = runner.invoke(blockchain, [
|
|
'validators'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Assertions
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert len(data['validators']) == 2
|
|
assert data['validators'][0]['stake'] == '100000'
|
|
|
|
# Verify API call
|
|
mock_client.get.assert_called_once_with(
|
|
'http://test:8000/v1/blockchain/validators',
|
|
headers={"X-Api-Key": "test_api_key"}
|
|
)
|
|
|
|
@patch('aitbc_cli.commands.blockchain.httpx.Client')
|
|
def test_api_error_handling(self, mock_client_class, runner, mock_config):
|
|
"""Test API error handling"""
|
|
# Setup mock for error response
|
|
mock_client = Mock()
|
|
mock_client_class.return_value.__enter__.return_value = mock_client
|
|
mock_response = Mock()
|
|
mock_response.status_code = 404
|
|
mock_client.get.return_value = mock_response
|
|
|
|
# Run command
|
|
result = runner.invoke(blockchain, [
|
|
'block',
|
|
'999999'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Assertions
|
|
assert result.exit_code == 0 # The command doesn't exit on error
|
|
assert 'not found' in result.output
|