Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Documentation Validation / validate-docs (push) Has been cancelled
Documentation Validation / validate-policies-strict (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Coverage Phase 1 (70% Target) / test-coverage-70 (push) Has been cancelled
Coverage Phase 2 (85% Target) / test-coverage-85 (push) Has been cancelled
Python Tests / test-python (push) Has been cancelled
- ssh-access-patterns.md: Clarify ns3/aitbc container setup with correct paths and service names - Add container hostname verification command - Update paths: /etc/aitbc/blockchain.env, /opt/aitbc/apps/blockchain-node/ - Fix service name: aitbc-blockchain-node (not aitbc-blockchain-node-3) - Add service restart and log viewing examples - test_workflow.sh: Rewrite as comprehensive integration test suite - Add
338 lines
12 KiB
Python
338 lines
12 KiB
Python
"""Integration tests for simulate CLI commands
|
|
|
|
These tests require coordinator-api running and validate simulation operations
|
|
including blockchain, wallets, price, network, and ai-jobs simulations with actual service calls.
|
|
"""
|
|
|
|
import pytest
|
|
import json
|
|
import httpx
|
|
from click.testing import CliRunner
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
from aitbc_cli.commands.simulate import simulate
|
|
from aitbc import AITBCHTTPClient, NetworkError
|
|
|
|
|
|
@pytest.fixture
|
|
def runner():
|
|
"""Create CLI runner"""
|
|
return CliRunner()
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_config():
|
|
"""Mock configuration"""
|
|
config = Mock()
|
|
config.coordinator_url = "http://127.0.0.1:18000"
|
|
config.api_key = "test_api_key"
|
|
return config
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_http_client():
|
|
"""Mock HTTP client for coordinator-api"""
|
|
client = MagicMock(spec=AITBCHTTPClient)
|
|
return client
|
|
|
|
|
|
class TestSimulateCommandsIntegration:
|
|
"""Integration tests for simulate commands with coordinator-api"""
|
|
|
|
@pytest.fixture
|
|
def coordinator_available(self):
|
|
"""Skip test if coordinator-api is not running"""
|
|
try:
|
|
response = httpx.get("http://127.0.0.1:18000/health", timeout=2)
|
|
if response.status_code == 200:
|
|
return True
|
|
except Exception:
|
|
pytest.skip("coordinator-api not running at http://127.0.0.1:18000")
|
|
|
|
def test_simulate_blockchain(self, runner, mock_config, coordinator_available):
|
|
"""Test blockchain simulation"""
|
|
result = runner.invoke(simulate, [
|
|
'blockchain',
|
|
'--blocks', '10',
|
|
'--transactions', '50'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'blocks' in data or 'simulation_id' in data
|
|
|
|
def test_simulate_wallets(self, runner, mock_config, coordinator_available):
|
|
"""Test wallet simulation"""
|
|
result = runner.invoke(simulate, [
|
|
'wallets',
|
|
'--count', '5',
|
|
'--balance', '1000'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'wallets' in data or 'simulation_id' in data
|
|
|
|
def test_simulate_price(self, runner, mock_config, coordinator_available):
|
|
"""Test price simulation"""
|
|
result = runner.invoke(simulate, [
|
|
'price',
|
|
'--days', '30',
|
|
'--volatility', '0.1'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'prices' in data or 'simulation_id' in data
|
|
|
|
def test_simulate_network(self, runner, mock_config, coordinator_available):
|
|
"""Test network simulation"""
|
|
result = runner.invoke(simulate, [
|
|
'network',
|
|
'--nodes', '10',
|
|
'--latency', '50'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'network' in data or 'simulation_id' in data
|
|
|
|
def test_simulate_ai_jobs(self, runner, mock_config, coordinator_available):
|
|
"""Test AI jobs simulation"""
|
|
result = runner.invoke(simulate, [
|
|
'ai-jobs',
|
|
'--jobs', '20',
|
|
'--duration', '300'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'jobs' in data or 'simulation_id' in data
|
|
|
|
def test_simulate_run(self, runner, mock_config, coordinator_available):
|
|
"""Test running a simulation"""
|
|
result = runner.invoke(simulate, [
|
|
'run',
|
|
'--type', 'blockchain',
|
|
'--duration', '60'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'simulation_id' in data or 'status' in data
|
|
|
|
def test_simulate_status(self, runner, mock_config, coordinator_available):
|
|
"""Test getting simulation status"""
|
|
# First run a simulation
|
|
run_result = runner.invoke(simulate, [
|
|
'run',
|
|
'--type', 'blockchain'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert run_result.exit_code == 0
|
|
run_data = json.loads(run_result.output)
|
|
sim_id = run_data.get('simulation_id')
|
|
|
|
if sim_id:
|
|
# Get status
|
|
status_result = runner.invoke(simulate, [
|
|
'status', sim_id
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert status_result.exit_code == 0
|
|
status_data = json.loads(status_result.output)
|
|
assert 'status' in status_data
|
|
|
|
def test_simulate_result(self, runner, mock_config, coordinator_available):
|
|
"""Test getting simulation results"""
|
|
# First run a simulation
|
|
run_result = runner.invoke(simulate, [
|
|
'run',
|
|
'--type', 'wallets'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert run_result.exit_code == 0
|
|
run_data = json.loads(run_result.output)
|
|
sim_id = run_data.get('simulation_id')
|
|
|
|
if sim_id:
|
|
# Get results
|
|
result_result = runner.invoke(simulate, [
|
|
'result', sim_id
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result_result.exit_code == 0
|
|
result_data = json.loads(result_result.output)
|
|
assert 'results' in result_data or 'data' in result_data
|
|
|
|
def test_simulate_blockchain_with_params(self, runner, mock_config, coordinator_available):
|
|
"""Test blockchain simulation with custom parameters"""
|
|
result = runner.invoke(simulate, [
|
|
'blockchain',
|
|
'--blocks', '100',
|
|
'--transactions', '500',
|
|
'--difficulty', '5'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'blocks' in data or 'simulation_id' in data
|
|
|
|
def test_simulate_wallets_with_distribution(self, runner, mock_config, coordinator_available):
|
|
"""Test wallet simulation with balance distribution"""
|
|
result = runner.invoke(simulate, [
|
|
'wallets',
|
|
'--count', '10',
|
|
'--distribution', 'exponential'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'wallets' in data or 'simulation_id' in data
|
|
|
|
def test_simulate_price_with_trend(self, runner, mock_config, coordinator_available):
|
|
"""Test price simulation with trend"""
|
|
result = runner.invoke(simulate, [
|
|
'price',
|
|
'--days', '90',
|
|
'--trend', 'bullish',
|
|
'--volatility', '0.15'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'prices' in data or 'simulation_id' in data
|
|
|
|
def test_simulate_network_with_topology(self, runner, mock_config, coordinator_available):
|
|
"""Test network simulation with custom topology"""
|
|
result = runner.invoke(simulate, [
|
|
'network',
|
|
'--nodes', '20',
|
|
'--topology', 'mesh',
|
|
'--latency', '100'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'network' in data or 'simulation_id' in data
|
|
|
|
def test_simulate_ai_jobs_with_gpu(self, runner, mock_config, coordinator_available):
|
|
"""Test AI jobs simulation with GPU requirements"""
|
|
result = runner.invoke(simulate, [
|
|
'ai-jobs',
|
|
'--jobs', '30',
|
|
'--gpu-required',
|
|
'--duration', '600'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'jobs' in data or 'simulation_id' in data
|
|
|
|
def test_simulate_run_async(self, runner, mock_config, coordinator_available):
|
|
"""Test running simulation in async mode"""
|
|
result = runner.invoke(simulate, [
|
|
'run',
|
|
'--type', 'network',
|
|
'--async',
|
|
'--duration', '120'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
assert 'simulation_id' in data
|
|
assert data.get('status') in ['started', 'running', 'pending']
|
|
|
|
def test_simulate_status_nonexistent(self, runner, mock_config):
|
|
"""Test getting status of non-existent simulation"""
|
|
result = runner.invoke(simulate, [
|
|
'status', 'sim_nonexistent_12345'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Should handle gracefully
|
|
assert result.exit_code != 0 or 'not found' in result.output.lower()
|
|
|
|
def test_simulate_result_nonexistent(self, runner, mock_config):
|
|
"""Test getting results of non-existent simulation"""
|
|
result = runner.invoke(simulate, [
|
|
'result', 'sim_nonexistent_12345'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Should handle gracefully
|
|
assert result.exit_code != 0 or 'not found' in result.output.lower()
|
|
|
|
def test_simulate_multiple_concurrent(self, runner, mock_config, coordinator_available):
|
|
"""Test running multiple concurrent simulations"""
|
|
sim_ids = []
|
|
|
|
for i in range(3):
|
|
result = runner.invoke(simulate, [
|
|
'run',
|
|
'--type', 'blockchain',
|
|
'--async'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
data = json.loads(result.output)
|
|
sim_id = data.get('simulation_id')
|
|
if sim_id:
|
|
sim_ids.append(sim_id)
|
|
|
|
# Verify we got multiple simulation IDs
|
|
assert len(sim_ids) > 0
|
|
|
|
def test_simulate_api_error_handling(self, runner, mock_config):
|
|
"""Test simulate command handles coordinator-api errors gracefully"""
|
|
# Use invalid coordinator URL to trigger error
|
|
mock_config.coordinator_url = "http://invalid:9999"
|
|
|
|
result = runner.invoke(simulate, [
|
|
'blockchain'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
# Should either fail gracefully or skip with appropriate message
|
|
assert result.exit_code != 0 or 'error' in result.output.lower() or 'unavailable' in result.output.lower()
|
|
|
|
@patch('aitbc_cli.commands.simulate.get_config')
|
|
@patch('aitbc_cli.commands.simulate.AITBCHTTPClient')
|
|
def test_simulate_blockchain_via_coordinator_api(self, mock_http_client_class, mock_get_config, runner):
|
|
"""Test blockchain simulation via coordinator-api"""
|
|
# Setup mocks
|
|
mock_config = Mock()
|
|
mock_config.coordinator_url = "http://127.0.0.1:18000"
|
|
mock_get_config.return_value = mock_config
|
|
|
|
mock_client = MagicMock()
|
|
mock_http_client_class.return_value = mock_client
|
|
mock_client.post.return_value = {
|
|
"simulation_id": "sim_123",
|
|
"status": "started",
|
|
"blocks": 10
|
|
}
|
|
|
|
result = runner.invoke(simulate, [
|
|
'blockchain',
|
|
'--blocks', '10'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result.exit_code == 0
|
|
# Verify API was called (if simulate command uses coordinator-api)
|
|
|
|
def test_simulate_output_formats(self, runner, mock_config, coordinator_available):
|
|
"""Test simulation output in different formats"""
|
|
# JSON format
|
|
result_json = runner.invoke(simulate, [
|
|
'blockchain',
|
|
'--blocks', '5'
|
|
], obj={'config': mock_config, 'output_format': 'json'})
|
|
|
|
assert result_json.exit_code == 0
|
|
json.loads(result_json.output) # Should be valid JSON
|
|
|
|
# Table format
|
|
result_table = runner.invoke(simulate, [
|
|
'blockchain',
|
|
'--blocks', '5'
|
|
], obj={'config': mock_config, 'output_format': 'table'})
|
|
|
|
assert result_table.exit_code == 0
|