feat: add miner management endpoints and standardize all API paths to /api/v1 prefix

- Add POST /api/v1/miners/{miner_id}/jobs endpoint for listing miner-assigned jobs with filtering
- Add POST /api/v1/miners/{miner_id}/earnings endpoint for miner earnings tracking (mock implementation)
- Add PUT /api/v1/miners/{miner_id}/capabilities endpoint for updating miner capabilities
- Add DELETE /api/v1/miners/{miner_id} endpoint for miner deregistration (sets OFFLINE status)
- Add JobService.fail_job()
This commit is contained in:
oib
2026-03-05 11:12:57 +01:00
parent c2d4f39a36
commit 80b9ea4b25
11 changed files with 1664 additions and 85 deletions

View File

@@ -0,0 +1,401 @@
"""Tests for deployment commands"""
import pytest
import json
import asyncio
from click.testing import CliRunner
from unittest.mock import Mock, patch, AsyncMock
from aitbc_cli.main import cli
@pytest.fixture
def runner():
"""Create CLI runner"""
return CliRunner()
@pytest.fixture
def mock_config():
"""Mock configuration for testing"""
return {
'coordinator_url': 'http://localhost:8000',
'api_key': 'test-key',
'wallet_name': 'test-wallet'
}
class TestDeployCommands:
"""Test suite for deployment operations"""
def test_deploy_create_success(self, runner, mock_config):
"""Test successful deployment configuration creation"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = Mock()
mock_deployment.create_deployment = AsyncMock(return_value='deploy_123')
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'create',
'test-app', 'production', 'us-west-1', 't3.medium',
'2', '5', '3', '8080', 'app.example.com',
'--db-host', 'db.example.com',
'--db-port', '5432',
'--db-name', 'aitbc_prod'
])
assert result.exit_code == 0
assert 'deployment configuration created' in result.output.lower()
assert 'deploy_123' in result.output
mock_deployment.create_deployment.assert_called_once()
def test_deploy_create_failure(self, runner, mock_config):
"""Test deployment configuration creation failure"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = Mock()
mock_deployment.create_deployment = AsyncMock(return_value=None)
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'create',
'test-app', 'production', 'us-west-1', 't3.medium',
'2', '5', '3', '8080', 'app.example.com'
])
assert result.exit_code == 1
assert 'failed to create deployment' in result.output.lower()
def test_deploy_create_exception(self, runner, mock_config):
"""Test deployment configuration creation with exception"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = Mock()
mock_deployment.create_deployment = AsyncMock(side_effect=Exception("Network error"))
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'create',
'test-app', 'production', 'us-west-1', 't3.medium',
'2', '5', '3', '8080', 'app.example.com'
])
assert result.exit_code == 1
assert 'error creating deployment' in result.output.lower()
def test_deploy_start_success(self, runner, mock_config):
"""Test successful deployment start"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = Mock()
mock_deployment.deploy_application = AsyncMock(return_value=True)
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'start',
'deploy_123'
])
assert result.exit_code == 0
assert 'deploy_123 started successfully' in result.output.lower()
mock_deployment.deploy_application.assert_called_once_with('deploy_123')
def test_deploy_start_failure(self, runner, mock_config):
"""Test deployment start failure"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = Mock()
mock_deployment.deploy_application = AsyncMock(return_value=False)
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'start',
'deploy_123'
])
assert result.exit_code == 1
assert 'failed to start deployment' in result.output.lower()
def test_deploy_scale_success(self, runner, mock_config):
"""Test successful deployment scaling"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = Mock()
mock_deployment.scale_deployment = AsyncMock(return_value=True)
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'scale',
'deploy_123', '5',
'--reason', 'high_traffic'
])
assert result.exit_code == 0
assert 'deploy_123 scaled to 5 instances' in result.output.lower()
mock_deployment.scale_deployment.assert_called_once_with('deploy_123', 5, 'high_traffic')
def test_deploy_scale_failure(self, runner, mock_config):
"""Test deployment scaling failure"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = Mock()
mock_deployment.scale_deployment = AsyncMock(return_value=False)
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'scale',
'deploy_123', '5'
])
assert result.exit_code == 1
assert 'failed to scale deployment' in result.output.lower()
def test_deploy_status_success(self, runner, mock_config):
"""Test successful deployment status retrieval"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.asyncio.run') as mock_run:
mock_run.return_value = {
'deployment_id': 'deploy_123',
'status': 'running',
'instances': 3,
'healthy_instances': 3,
'cpu_usage': 45.2,
'memory_usage': 67.8,
'last_updated': '2023-01-01T12:00:00Z'
}
result = runner.invoke(cli, [
'deploy', 'status',
'deploy_123'
])
assert result.exit_code == 0
assert 'deploy_123' in result.output
assert 'running' in result.output
def test_deploy_status_not_found(self, runner, mock_config):
"""Test deployment status for non-existent deployment"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.asyncio.run') as mock_run:
mock_run.return_value = None
result = runner.invoke(cli, [
'deploy', 'status',
'non_existent'
])
assert result.exit_code == 1
assert 'deployment not found' in result.output.lower()
def test_deploy_overview_success(self, runner, mock_config):
"""Test successful deployment overview retrieval"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.asyncio.run') as mock_run:
mock_run.return_value = {
'total_deployments': 5,
'running_deployments': 3,
'failed_deployments': 0,
'total_instances': 15,
'active_regions': ['us-west-1', 'us-east-1'],
'cluster_health': 'healthy',
'last_updated': '2023-01-01T12:00:00Z'
}
result = runner.invoke(cli, [
'deploy', 'overview',
'--format', 'json'
])
assert result.exit_code == 0
assert '5' in result.output # Total deployments
assert 'healthy' in result.output.lower()
def test_deploy_overview_table_format(self, runner, mock_config):
"""Test deployment overview in table format"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.asyncio.run') as mock_run:
mock_run.return_value = {
'total_deployments': 5,
'running_deployments': 3,
'failed_deployments': 0,
'total_instances': 15,
'active_regions': ['us-west-1', 'us-east-1'],
'cluster_health': 'healthy',
'last_updated': '2023-01-01T12:00:00Z'
}
result = runner.invoke(cli, [
'deploy', 'overview'
])
assert result.exit_code == 0
assert 'total_deployments' in result.output.lower()
assert '5' in result.output
def test_deploy_monitor_success(self, runner, mock_config):
"""Test successful deployment monitoring"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.asyncio.run') as mock_run:
mock_run.return_value = {
'deployment_id': 'deploy_123',
'status': 'running',
'instances': [
{'id': 'i-123', 'status': 'healthy', 'cpu': 45.2, 'memory': 67.8},
{'id': 'i-456', 'status': 'healthy', 'cpu': 38.1, 'memory': 52.3},
{'id': 'i-789', 'status': 'healthy', 'cpu': 52.7, 'memory': 71.4}
],
'alerts': [],
'last_updated': '2023-01-01T12:00:00Z'
}
# Mock the monitoring loop to run only once
with patch('time.sleep', side_effect=KeyboardInterrupt):
result = runner.invoke(cli, [
'deploy', 'monitor',
'deploy_123',
'--interval', '1'
])
assert result.exit_code == 0
assert 'deploy_123' in result.output
def test_deploy_auto_scale_success(self, runner, mock_config):
"""Test successful auto-scaling evaluation"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.asyncio.run') as mock_run:
mock_run.return_value = {
'deployment_id': 'deploy_123',
'evaluation': 'scale_up',
'current_instances': 3,
'recommended_instances': 5,
'reason': 'High CPU usage detected',
'metrics': {
'avg_cpu': 85.2,
'avg_memory': 72.1,
'request_rate': 1500
}
}
result = runner.invoke(cli, [
'deploy', 'auto-scale',
'deploy_123'
])
assert result.exit_code == 0
assert 'auto-scaling evaluation completed' in result.output.lower()
assert 'scale_up' in result.output
def test_deploy_auto_scale_no_action(self, runner, mock_config):
"""Test auto-scaling evaluation with no action needed"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.asyncio.run') as mock_run:
mock_run.return_value = {
'deployment_id': 'deploy_123',
'evaluation': 'no_action',
'current_instances': 3,
'recommended_instances': 3,
'reason': 'Metrics within normal range',
'metrics': {
'avg_cpu': 45.2,
'avg_memory': 52.1,
'request_rate': 500
}
}
result = runner.invoke(cli, [
'deploy', 'auto-scale',
'deploy_123'
])
assert result.exit_code == 0
assert 'no scaling action needed' in result.output.lower()
def test_deploy_list_deployments_success(self, runner, mock_config):
"""Test successful deployment listing"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.asyncio.run') as mock_run:
mock_run.return_value = [
{
'deployment_id': 'deploy_123',
'name': 'web-app',
'environment': 'production',
'status': 'running',
'instances': 3,
'region': 'us-west-1'
},
{
'deployment_id': 'deploy_456',
'name': 'api-service',
'environment': 'staging',
'status': 'stopped',
'instances': 0,
'region': 'us-east-1'
}
]
result = runner.invoke(cli, [
'deploy', 'list-deployments',
'--format', 'table'
])
assert result.exit_code == 0
assert 'deploy_123' in result.output
assert 'web-app' in result.output
assert 'deploy_456' in result.output
assert 'api-service' in result.output
def test_deploy_list_deployments_empty(self, runner, mock_config):
"""Test deployment listing with no deployments"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.asyncio.run') as mock_run:
mock_run.return_value = []
result = runner.invoke(cli, [
'deploy', 'list-deployments'
])
assert result.exit_code == 0
assert 'no deployments found' in result.output.lower()
def test_deploy_list_deployments_json_format(self, runner, mock_config):
"""Test deployment listing in JSON format"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.asyncio.run') as mock_run:
mock_run.return_value = [
{
'deployment_id': 'deploy_123',
'name': 'web-app',
'environment': 'production',
'status': 'running',
'instances': 3,
'region': 'us-west-1'
}
]
result = runner.invoke(cli, [
'deploy', 'list-deployments',
'--format', 'json'
])
assert result.exit_code == 0
# Should be valid JSON
json_data = json.loads(result.output)
assert len(json_data) == 1
assert json_data[0]['deployment_id'] == 'deploy_123'

View File

@@ -0,0 +1,405 @@
"""Tests for deployment commands - simplified version"""
import pytest
import json
from click.testing import CliRunner
from unittest.mock import Mock, patch, MagicMock
from aitbc_cli.main import cli
@pytest.fixture
def runner():
"""Create CLI runner"""
return CliRunner()
@pytest.fixture
def mock_config():
"""Mock configuration for testing"""
return {
'coordinator_url': 'http://localhost:8000',
'api_key': 'test-key',
'wallet_name': 'test-wallet'
}
class TestDeployCommands:
"""Test suite for deployment operations"""
def test_deploy_create_success(self, runner, mock_config):
"""Test successful deployment configuration creation"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.create_deployment.return_value = 'deploy_123'
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'create',
'test-app', 'production', 'us-west-1', 't3.medium',
'2', '5', '3', '8080', 'app.example.com'
])
assert result.exit_code == 0
assert 'deployment configuration created' in result.output.lower()
assert 'deploy_123' in result.output
def test_deploy_create_failure(self, runner, mock_config):
"""Test deployment configuration creation failure"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.create_deployment.return_value = None
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'create',
'test-app', 'production', 'us-west-1', 't3.medium',
'2', '5', '3', '8080', 'app.example.com'
])
assert result.exit_code == 1
assert 'failed to create deployment' in result.output.lower()
def test_deploy_start_success(self, runner, mock_config):
"""Test successful deployment start"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.deploy_application.return_value = True
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'start',
'deploy_123'
])
assert result.exit_code == 0
assert 'deploy_123 started successfully' in result.output.lower()
def test_deploy_start_failure(self, runner, mock_config):
"""Test deployment start failure"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.deploy_application.return_value = False
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'start',
'deploy_123'
])
assert result.exit_code == 1
assert 'failed to start deployment' in result.output.lower()
def test_deploy_scale_success(self, runner, mock_config):
"""Test successful deployment scaling"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.scale_deployment.return_value = True
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'scale',
'deploy_123', '5',
'--reason', 'high_traffic'
])
assert result.exit_code == 0
assert 'deploy_123 scaled to 5 instances' in result.output.lower()
def test_deploy_scale_failure(self, runner, mock_config):
"""Test deployment scaling failure"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.scale_deployment.return_value = False
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'scale',
'deploy_123', '5'
])
assert result.exit_code == 1
assert 'failed to scale deployment' in result.output.lower()
def test_deploy_status_success(self, runner, mock_config):
"""Test successful deployment status retrieval"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.get_deployment_status.return_value = {
'deployment': {
'deployment_id': 'deploy_123',
'name': 'test-app',
'environment': 'production',
'region': 'us-west-1',
'status': 'running',
'instances': 3
},
'instances': [
{'id': 'i-123', 'status': 'healthy', 'cpu': 45.2, 'memory': 67.8},
{'id': 'i-456', 'status': 'healthy', 'cpu': 38.1, 'memory': 52.3}
],
'metrics': {
'cpu_usage': 45.2,
'memory_usage': 67.8,
'request_rate': 1500
}
}
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'status',
'deploy_123'
])
assert result.exit_code == 0
assert 'deploy_123' in result.output
assert 'running' in result.output
def test_deploy_status_not_found(self, runner, mock_config):
"""Test deployment status for non-existent deployment"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.get_deployment_status.return_value = None
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'status',
'non_existent'
])
assert result.exit_code == 1
assert 'deployment not found' in result.output.lower()
def test_deploy_overview_success(self, runner, mock_config):
"""Test successful deployment overview retrieval"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.get_cluster_overview.return_value = {
'total_deployments': 5,
'running_deployments': 3,
'failed_deployments': 0,
'total_instances': 15,
'active_regions': ['us-west-1', 'us-east-1'],
'cluster_health': 'healthy',
'last_updated': '2023-01-01T12:00:00Z'
}
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'overview',
'--format', 'json'
])
assert result.exit_code == 0
assert '5' in result.output # Total deployments
assert 'healthy' in result.output.lower()
def test_deploy_overview_table_format(self, runner, mock_config):
"""Test deployment overview in table format"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.get_cluster_overview.return_value = {
'total_deployments': 5,
'running_deployments': 3,
'failed_deployments': 0,
'total_instances': 15,
'active_regions': ['us-west-1', 'us-east-1'],
'cluster_health': 'healthy',
'last_updated': '2023-01-01T12:00:00Z'
}
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'overview'
])
assert result.exit_code == 0
assert 'total_deployments' in result.output.lower()
assert '5' in result.output
def test_deploy_monitor_success(self, runner, mock_config):
"""Test successful deployment monitoring"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.monitor_deployment.return_value = {
'deployment_id': 'deploy_123',
'status': 'running',
'instances': [
{'id': 'i-123', 'status': 'healthy', 'cpu': 45.2, 'memory': 67.8},
{'id': 'i-456', 'status': 'healthy', 'cpu': 38.1, 'memory': 52.3}
],
'alerts': [],
'last_updated': '2023-01-01T12:00:00Z'
}
mock_deployment_class.return_value = mock_deployment
# Mock the monitoring loop to run only once
with patch('time.sleep', side_effect=KeyboardInterrupt):
result = runner.invoke(cli, [
'deploy', 'monitor',
'deploy_123',
'--interval', '1'
])
assert result.exit_code == 0
assert 'deploy_123' in result.output
def test_deploy_auto_scale_success(self, runner, mock_config):
"""Test successful auto-scaling evaluation"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.evaluate_auto_scaling.return_value = {
'deployment_id': 'deploy_123',
'evaluation': 'scale_up',
'current_instances': 3,
'recommended_instances': 5,
'reason': 'High CPU usage detected',
'metrics': {
'avg_cpu': 85.2,
'avg_memory': 72.1,
'request_rate': 1500
}
}
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'auto-scale',
'deploy_123'
])
assert result.exit_code == 0
assert 'auto-scaling evaluation completed' in result.output.lower()
assert 'scale_up' in result.output
def test_deploy_auto_scale_no_action(self, runner, mock_config):
"""Test auto-scaling evaluation with no action needed"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.evaluate_auto_scaling.return_value = {
'deployment_id': 'deploy_123',
'evaluation': 'no_action',
'current_instances': 3,
'recommended_instances': 3,
'reason': 'Metrics within normal range',
'metrics': {
'avg_cpu': 45.2,
'avg_memory': 52.1,
'request_rate': 500
}
}
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'auto-scale',
'deploy_123'
])
assert result.exit_code == 0
assert 'no scaling action needed' in result.output.lower()
def test_deploy_list_deployments_success(self, runner, mock_config):
"""Test successful deployment listing"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.list_all_deployments.return_value = [
{
'deployment_id': 'deploy_123',
'name': 'web-app',
'environment': 'production',
'status': 'running',
'instances': 3,
'region': 'us-west-1'
},
{
'deployment_id': 'deploy_456',
'name': 'api-service',
'environment': 'staging',
'status': 'stopped',
'instances': 0,
'region': 'us-east-1'
}
]
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'list-deployments',
'--format', 'table'
])
assert result.exit_code == 0
assert 'deploy_123' in result.output
assert 'web-app' in result.output
assert 'deploy_456' in result.output
assert 'api-service' in result.output
def test_deploy_list_deployments_empty(self, runner, mock_config):
"""Test deployment listing with no deployments"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.list_all_deployments.return_value = []
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'list-deployments'
])
assert result.exit_code == 0
assert 'no deployments found' in result.output.lower()
def test_deploy_list_deployments_json_format(self, runner, mock_config):
"""Test deployment listing in JSON format"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('aitbc_cli.commands.deployment.ProductionDeployment') as mock_deployment_class:
mock_deployment = MagicMock()
mock_deployment.list_all_deployments.return_value = [
{
'deployment_id': 'deploy_123',
'name': 'web-app',
'environment': 'production',
'status': 'running',
'instances': 3,
'region': 'us-west-1'
}
]
mock_deployment_class.return_value = mock_deployment
result = runner.invoke(cli, [
'deploy', 'list-deployments',
'--format', 'json'
])
assert result.exit_code == 0
# Should be valid JSON
json_data = json.loads(result.output)
assert len(json_data) == 1
assert json_data[0]['deployment_id'] == 'deploy_123'

View File

@@ -0,0 +1,138 @@
"""Tests for deployment commands - structure only"""
import pytest
from click.testing import CliRunner
from aitbc_cli.main import cli
@pytest.fixture
def runner():
"""Create CLI runner"""
return CliRunner()
class TestDeployCommands:
"""Test suite for deployment operations"""
def test_deploy_create_help(self, runner):
"""Test deploy create help command"""
result = runner.invoke(cli, ['deploy', 'create', '--help'])
assert result.exit_code == 0
assert 'create a new deployment configuration' in result.output.lower()
assert 'name' in result.output.lower()
assert 'environment' in result.output.lower()
assert 'region' in result.output.lower()
def test_deploy_start_help(self, runner):
"""Test deploy start help command"""
result = runner.invoke(cli, ['deploy', 'start', '--help'])
assert result.exit_code == 0
assert 'deploy the application to production' in result.output.lower()
assert 'deployment_id' in result.output.lower()
def test_deploy_scale_help(self, runner):
"""Test deploy scale help command"""
result = runner.invoke(cli, ['deploy', 'scale', '--help'])
assert result.exit_code == 0
assert 'scale a deployment' in result.output.lower()
assert 'target_instances' in result.output.lower()
assert 'reason' in result.output.lower()
def test_deploy_status_help(self, runner):
"""Test deploy status help command"""
result = runner.invoke(cli, ['deploy', 'status', '--help'])
assert result.exit_code == 0
assert 'comprehensive deployment status' in result.output.lower()
assert 'deployment_id' in result.output.lower()
def test_deploy_overview_help(self, runner):
"""Test deploy overview help command"""
result = runner.invoke(cli, ['deploy', 'overview', '--help'])
assert result.exit_code == 0
assert 'overview of all deployments' in result.output.lower()
assert 'format' in result.output.lower()
def test_deploy_monitor_help(self, runner):
"""Test deploy monitor help command"""
result = runner.invoke(cli, ['deploy', 'monitor', '--help'])
assert result.exit_code == 0
assert 'monitor deployment performance' in result.output.lower()
assert 'interval' in result.output.lower()
def test_deploy_auto_scale_help(self, runner):
"""Test deploy auto-scale help command"""
result = runner.invoke(cli, ['deploy', 'auto-scale', '--help'])
assert result.exit_code == 0
assert 'auto-scaling evaluation' in result.output.lower()
assert 'deployment_id' in result.output.lower()
def test_deploy_list_deployments_help(self, runner):
"""Test deploy list-deployments help command"""
result = runner.invoke(cli, ['deploy', 'list-deployments', '--help'])
assert result.exit_code == 0
assert 'list all deployments' in result.output.lower()
assert 'format' in result.output.lower()
def test_deploy_group_help(self, runner):
"""Test deploy group help command"""
result = runner.invoke(cli, ['deploy', '--help'])
assert result.exit_code == 0
assert 'production deployment and scaling commands' in result.output.lower()
assert 'create' in result.output.lower()
assert 'start' in result.output.lower()
assert 'scale' in result.output.lower()
assert 'status' in result.output.lower()
assert 'overview' in result.output.lower()
assert 'monitor' in result.output.lower()
assert 'auto-scale' in result.output.lower()
assert 'list-deployments' in result.output.lower()
def test_deploy_create_missing_args(self, runner):
"""Test deploy create with missing arguments"""
result = runner.invoke(cli, ['deploy', 'create'])
assert result.exit_code == 2
assert 'missing argument' in result.output.lower() or 'usage:' in result.output.lower()
def test_deploy_start_missing_args(self, runner):
"""Test deploy start with missing arguments"""
result = runner.invoke(cli, ['deploy', 'start'])
assert result.exit_code == 2
assert 'missing argument' in result.output.lower() or 'usage:' in result.output.lower()
def test_deploy_scale_missing_args(self, runner):
"""Test deploy scale with missing arguments"""
result = runner.invoke(cli, ['deploy', 'scale'])
assert result.exit_code == 2
assert 'missing argument' in result.output.lower() or 'usage:' in result.output.lower()
def test_deploy_status_missing_args(self, runner):
"""Test deploy status with missing arguments"""
result = runner.invoke(cli, ['deploy', 'status'])
assert result.exit_code == 2
assert 'missing argument' in result.output.lower() or 'usage:' in result.output.lower()
def test_deploy_monitor_missing_args(self, runner):
"""Test deploy monitor with missing arguments"""
result = runner.invoke(cli, ['deploy', 'monitor'])
assert result.exit_code == 2
assert 'missing argument' in result.output.lower() or 'usage:' in result.output.lower()
def test_deploy_auto_scale_missing_args(self, runner):
"""Test deploy auto-scale with missing arguments"""
result = runner.invoke(cli, ['deploy', 'auto-scale'])
assert result.exit_code == 2
assert 'missing argument' in result.output.lower() or 'usage:' in result.output.lower()
def test_deploy_overview_no_args(self, runner):
"""Test deploy overview with no arguments (should work)"""
result = runner.invoke(cli, ['deploy', 'overview'])
# The command works and returns empty deployment data
assert result.exit_code == 0
assert 'total deployments' in result.output.lower()
def test_deploy_list_deployments_no_args(self, runner):
"""Test deploy list-deployments with no arguments (should work)"""
result = runner.invoke(cli, ['deploy', 'list-deployments'])
# The command works and returns no deployments
assert result.exit_code == 0
assert 'no deployments found' in result.output.lower()

View File

@@ -0,0 +1,497 @@
"""Tests for additional marketplace commands"""
import pytest
import json
from click.testing import CliRunner
from unittest.mock import Mock, patch
from aitbc_cli.main import cli
@pytest.fixture
def runner():
"""Create CLI runner"""
return CliRunner()
@pytest.fixture
def mock_config():
"""Mock configuration for testing"""
return {
'coordinator_url': 'http://localhost:8000',
'api_key': 'test-key',
'wallet_name': 'test-wallet'
}
class TestMarketplaceBidCommands:
"""Test suite for marketplace bid operations"""
def test_bid_submit_success(self, runner, mock_config):
"""Test successful bid submission"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.post') as mock_post:
mock_response = Mock()
mock_response.status_code = 202
mock_response.json.return_value = {
'id': 'bid_123',
'provider': 'miner123',
'capacity': 10,
'price': 0.5
}
mock_post.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'bid', 'submit',
'--provider', 'miner123',
'--capacity', '10',
'--price', '0.5',
'--notes', 'High performance GPU'
])
assert result.exit_code == 0
assert 'bid submitted successfully' in result.output.lower()
assert 'bid_123' in result.output
def test_bid_submit_invalid_capacity(self, runner, mock_config):
"""Test bid submission with invalid capacity"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
result = runner.invoke(cli, [
'marketplace', 'bid', 'submit',
'--provider', 'miner123',
'--capacity', '0',
'--price', '0.5'
])
assert 'capacity must be greater than 0' in result.output.lower()
def test_bid_submit_invalid_price(self, runner, mock_config):
"""Test bid submission with invalid price"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
result = runner.invoke(cli, [
'marketplace', 'bid', 'submit',
'--provider', 'miner123',
'--capacity', '10',
'--price', '-1'
])
assert 'price must be greater than 0' in result.output.lower()
def test_bid_list_success(self, runner, mock_config):
"""Test successful bid listing"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.get') as mock_get:
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
'bids': [
{
'id': 'bid_123',
'provider': 'miner123',
'capacity': 10,
'price': 0.5,
'status': 'pending'
},
{
'id': 'bid_456',
'provider': 'miner456',
'capacity': 5,
'price': 0.3,
'status': 'accepted'
}
]
}
mock_get.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'bid', 'list',
'--status', 'pending',
'--limit', '10'
])
assert result.exit_code == 0
assert 'bid_123' in result.output
def test_bid_details_success(self, runner, mock_config):
"""Test successful bid details retrieval"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.get') as mock_get:
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
'id': 'bid_123',
'provider': 'miner123',
'capacity': 10,
'price': 0.5,
'status': 'pending',
'created_at': '2023-01-01T00:00:00Z',
'notes': 'High performance GPU'
}
mock_get.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'bid', 'details',
'bid_123'
])
assert result.exit_code == 0
assert 'bid_123' in result.output
assert 'miner123' in result.output
def test_bid_details_not_found(self, runner, mock_config):
"""Test bid details for non-existent bid"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.get') as mock_get:
mock_response = Mock()
mock_response.status_code = 404
mock_get.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'bid', 'details',
'non_existent'
])
assert result.exit_code == 0
# Should handle 404 gracefully
class TestMarketplaceGovernanceCommands:
"""Test suite for marketplace governance operations"""
def test_governance_create_proposal_success(self, runner, mock_config):
"""Test successful governance proposal creation"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.post') as mock_post:
mock_response = Mock()
mock_response.status_code = 201
mock_response.json.return_value = {
'proposal_id': 'prop_123',
'title': 'Update GPU Pricing',
'status': 'active'
}
mock_post.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'governance', 'create-proposal',
'--title', 'Update GPU Pricing',
'--description', 'Adjust pricing based on market demand',
'--proposal-type', 'pricing_update',
'--params', '{"min_price": 0.1, "max_price": 2.0}',
'--voting-period', '48'
])
assert result.exit_code == 0
assert 'proposal created successfully' in result.output.lower()
assert 'prop_123' in result.output
def test_governance_create_proposal_invalid_json(self, runner, mock_config):
"""Test governance proposal creation with invalid JSON parameters"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
result = runner.invoke(cli, [
'marketplace', 'governance', 'create-proposal',
'--title', 'Update GPU Pricing',
'--description', 'Adjust pricing based on market demand',
'--proposal-type', 'pricing_update',
'--params', 'invalid json',
'--voting-period', '48'
])
assert 'invalid json parameters' in result.output.lower()
def test_governance_list_proposals_success(self, runner, mock_config):
"""Test successful governance proposals listing"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.get') as mock_get:
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
'proposals': [
{
'proposal_id': 'prop_123',
'title': 'Update GPU Pricing',
'status': 'active',
'votes_for': 15,
'votes_against': 3
},
{
'proposal_id': 'prop_456',
'title:': 'Add New GPU Category',
'status': 'completed',
'votes_for': 25,
'votes_against': 2
}
]
}
mock_get.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'governance', 'list-proposals',
'--status', 'active',
'--limit', '10'
])
assert result.exit_code == 0
assert 'prop_123' in result.output
def test_governance_vote_success(self, runner, mock_config):
"""Test successful governance voting"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.post') as mock_post:
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
'proposal_id': 'prop_123',
'vote': 'for',
'voter': 'user123',
'timestamp': '2023-01-01T12:00:00Z'
}
mock_post.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'governance', 'vote',
'--proposal-id', 'prop_123',
'--vote', 'for',
'--reason', 'Supports market stability'
])
assert result.exit_code == 0
assert 'vote recorded' in result.output.lower()
def test_governance_vote_invalid_choice(self, runner, mock_config):
"""Test governance voting with invalid vote choice"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
result = runner.invoke(cli, [
'marketplace', 'governance', 'vote',
'--proposal-id', 'prop_123',
'--vote', 'invalid',
'--reason', 'Test vote'
])
assert 'invalid vote choice' in result.output.lower()
class TestMarketplaceReviewCommands:
"""Test suite for marketplace review operations"""
def test_marketplace_reviews_success(self, runner, mock_config):
"""Test successful GPU reviews listing"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.get') as mock_get:
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
'reviews': [
{
'review_id': 'rev_123',
'gpu_id': 'gpu_123',
'rating': 5,
'comment': 'Excellent performance!',
'reviewer': 'user123',
'created_at': '2023-01-01T10:00:00Z'
},
{
'review_id': 'rev_456',
'gpu_id': 'gpu_123',
'rating': 4,
'comment': 'Good value for money',
'reviewer': 'user456',
'created_at': '2023-01-02T15:30:00Z'
}
],
'average_rating': 4.5,
'total_reviews': 2
}
mock_get.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'reviews',
'gpu_123',
'--limit', '10'
])
assert result.exit_code == 0
assert 'rev_123' in result.output
assert '4.5' in result.output # Average rating
def test_marketplace_reviews_not_found(self, runner, mock_config):
"""Test reviews for non-existent GPU"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.get') as mock_get:
mock_response = Mock()
mock_response.status_code = 404
mock_get.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'reviews',
'non_existent_gpu'
])
assert result.exit_code == 0
# Should handle 404 gracefully
def test_marketplace_review_add_success(self, runner, mock_config):
"""Test successful review addition"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.post') as mock_post:
mock_response = Mock()
mock_response.status_code = 201
mock_response.json.return_value = {
'review_id': 'rev_789',
'gpu_id': 'gpu_123',
'rating': 5,
'comment': 'Amazing GPU!',
'reviewer': 'user789'
}
mock_post.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'review',
'gpu_123',
'--rating', '5',
'--comment', 'Amazing GPU!'
])
assert result.exit_code == 0
assert 'review added successfully' in result.output.lower()
assert 'rev_789' in result.output
def test_marketplace_review_invalid_rating(self, runner, mock_config):
"""Test review addition with invalid rating"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
result = runner.invoke(cli, [
'marketplace', 'review',
'gpu_123',
'--rating', '6', # Invalid rating > 5
'--comment', 'Test review'
])
assert 'rating must be between 1 and 5' in result.output.lower()
def test_marketplace_review_missing_comment(self, runner, mock_config):
"""Test review addition without required comment"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
result = runner.invoke(cli, [
'marketplace', 'review',
'gpu_123',
'--rating', '5'
# Missing --comment
])
assert 'comment is required' in result.output.lower()
class TestMarketplaceTestCommands:
"""Test suite for marketplace testing operations"""
def test_marketplace_test_load_success(self, runner, mock_config):
"""Test successful marketplace load test"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.post') as mock_post:
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
'test_id': 'test_123',
'status': 'completed',
'duration': 30,
'total_requests': 1500,
'successful_requests': 1495,
'failed_requests': 5,
'average_response_time': 0.25
}
mock_post.return_value = mock_response
result = runner.invoke(cli, [
'marketplace', 'test', 'load',
'--concurrent-users', '20',
'--rps', '100',
'--duration', '60'
])
assert result.exit_code == 0
assert 'load test completed successfully' in result.output.lower()
assert 'test_123' in result.output
def test_marketplace_test_health_success(self, runner, mock_config):
"""Test successful marketplace health check"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.get') as mock_get:
def mock_response(url, **kwargs):
response = Mock()
if '/health' in url:
response.status_code = 200
response.json.return_value = {'status': 'healthy'}
elif '/marketplace/status' in url:
response.status_code = 200
response.json.return_value = {'active_gpus': 25, 'active_bids': 10}
elif '/agents/health' in url:
response.status_code = 200
response.json.return_value = {'active_agents': 5}
elif '/blockchain/health' in url:
response.status_code = 200
response.json.return_value = {'block_height': 12345, 'synced': True}
else:
response.status_code = 404
return response
mock_get.side_effect = mock_response
result = runner.invoke(cli, [
'marketplace', 'test', 'health'
])
assert result.exit_code == 0
assert 'healthy' in result.output.lower()
def test_marketplace_test_health_partial_failure(self, runner, mock_config):
"""Test marketplace health check with some endpoints failing"""
with patch('aitbc_cli.config.get_config') as mock_get_config:
mock_get_config.return_value = mock_config
with patch('httpx.Client.get') as mock_get:
def mock_response(url, **kwargs):
response = Mock()
if '/health' in url:
response.status_code = 200
response.json.return_value = {'status': 'healthy'}
elif '/marketplace/status' in url:
response.status_code = 500 # Failed endpoint
elif '/agents/health' in url:
response.status_code = 200
response.json.return_value = {'active_agents': 5}
elif '/blockchain/health' in url:
response.status_code = 200
response.json.return_value = {'block_height': 12345, 'synced': True}
else:
response.status_code = 404
return response
mock_get.side_effect = mock_response
result = runner.invoke(cli, [
'marketplace', 'test', 'health'
])
assert result.exit_code == 0
# Should show mixed health status