chore(security): enhance environment configuration, CI workflows, and wallet daemon with security improvements
- Restructure .env.example with security-focused documentation, service-specific environment file references, and AWS Secrets Manager integration - Update CLI tests workflow to single Python 3.13 version, add pytest-mock dependency, and consolidate test execution with coverage - Add comprehensive security validation to package publishing workflow with manual approval gates, secret scanning, and release
This commit is contained in:
@@ -6,5 +6,26 @@ then patches httpx.Client so every CLI command's HTTP call is routed
|
||||
through the ASGI transport instead of making real network requests.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
f
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
from click.testing import CliRunner
|
||||
from aitbc_cli.main import cli
|
||||
|
||||
|
||||
class TestCLIIntegration:
|
||||
"""Test CLI integration with coordinator"""
|
||||
|
||||
def test_cli_help(self):
|
||||
"""Test CLI help command"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['--help'])
|
||||
assert result.exit_code == 0
|
||||
assert 'aitbc' in result.output.lower()
|
||||
|
||||
def test_config_show(self):
|
||||
"""Test config show command"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['config-show'])
|
||||
assert result.exit_code == 0
|
||||
@@ -15,4 +15,56 @@ def runner():
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config():
|
||||
"""Mock configu
|
||||
"""Mock configuration for testing"""
|
||||
return {
|
||||
'coordinator_url': 'http://localhost:8000',
|
||||
'api_key': 'test-key',
|
||||
'wallet_name': 'test-wallet'
|
||||
}
|
||||
|
||||
|
||||
class TestMarketplaceCommands:
|
||||
"""Test suite for marketplace commands"""
|
||||
|
||||
def test_marketplace_help(self, runner):
|
||||
"""Test marketplace help command"""
|
||||
result = runner.invoke(cli, ['marketplace', '--help'])
|
||||
assert result.exit_code == 0
|
||||
assert 'marketplace' in result.output.lower()
|
||||
|
||||
def test_marketplace_list(self, runner, mock_config):
|
||||
"""Test marketplace listing command"""
|
||||
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 = {
|
||||
'offers': [
|
||||
{'id': 1, 'price': 0.1, 'gpu_type': 'RTX 3080'},
|
||||
{'id': 2, 'price': 0.15, 'gpu_type': 'RTX 3090'}
|
||||
]
|
||||
}
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
result = runner.invoke(cli, ['marketplace', 'offers', 'list'])
|
||||
assert result.exit_code == 0
|
||||
assert 'offers' in result.output.lower() or 'gpu' in result.output.lower()
|
||||
|
||||
def test_marketplace_gpu_pricing(self, runner, mock_config):
|
||||
"""Test marketplace GPU pricing command"""
|
||||
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 = {
|
||||
'gpu_model': 'RTX 3080',
|
||||
'avg_price': 0.12,
|
||||
'price_range': {'min': 0.08, 'max': 0.15}
|
||||
}
|
||||
mock_get.return_value = mock_response
|
||||
|
||||
result = runner.invoke(cli, ['marketplace', 'pricing', 'RTX 3080'])
|
||||
assert result.exit_code == 0
|
||||
assert 'price' in result.output.lower() or 'rtx' in result.output.lower()
|
||||
@@ -5,14 +5,25 @@ import json
|
||||
import base64
|
||||
from unittest.mock import Mock, patch
|
||||
from click.testing import CliRunner
|
||||
from aitbc_cli.commands.marketplace_advanced import advanced, models, analytics, trading, dispute
|
||||
from aitbc_cli.main import cli
|
||||
|
||||
|
||||
class TestModelsCommands:
|
||||
"""Test advanced model NFT operations commands"""
|
||||
class TestMarketplaceAdvanced:
|
||||
"""Test advanced marketplace commands"""
|
||||
|
||||
def setup_method(self):
|
||||
"""Setup test environment"""
|
||||
def test_marketplace_help(self):
|
||||
"""Test marketplace help command"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['marketplace', '--help'])
|
||||
assert result.exit_code == 0
|
||||
assert 'marketplace' in result.output.lower()
|
||||
|
||||
def test_marketplace_agents_help(self):
|
||||
"""Test marketplace agents help command"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['marketplace', 'agents', '--help'])
|
||||
assert result.exit_code == 0
|
||||
assert 'agents' in result.output.lower()
|
||||
self.runner = CliRunner()
|
||||
self.config = {
|
||||
'coordinator_url': 'http://test:8000',
|
||||
|
||||
@@ -100,7 +100,7 @@ class TestSimulateCommands:
|
||||
with patch('aitbc_cli.commands.simulate.Path') as mock_path_class:
|
||||
# Make Path return our temp directory
|
||||
mock_path_class.return_value = home_dir
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/home" else Path(x)
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/tests/e2e/fixtures/home" else Path(x)
|
||||
|
||||
# Run command
|
||||
result = runner.invoke(simulate, [
|
||||
@@ -129,7 +129,7 @@ class TestSimulateCommands:
|
||||
# Patch the hardcoded path
|
||||
with patch('aitbc_cli.commands.simulate.Path') as mock_path_class:
|
||||
mock_path_class.return_value = home_dir
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/home" else Path(x)
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/tests/e2e/fixtures/home" else Path(x)
|
||||
|
||||
# Run command
|
||||
result = runner.invoke(simulate, [
|
||||
@@ -151,7 +151,7 @@ class TestSimulateCommands:
|
||||
# Patch the hardcoded path
|
||||
with patch('aitbc_cli.commands.simulate.Path') as mock_path_class:
|
||||
mock_path_class.return_value = home_dir
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/home" else Path(x)
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/tests/e2e/fixtures/home" else Path(x)
|
||||
|
||||
# Run command
|
||||
result = runner.invoke(simulate, [
|
||||
@@ -182,7 +182,7 @@ class TestSimulateCommands:
|
||||
# Patch the hardcoded path
|
||||
with patch('aitbc_cli.commands.simulate.Path') as mock_path_class:
|
||||
mock_path_class.return_value = home_dir
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/home" else Path(x)
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/tests/e2e/fixtures/home" else Path(x)
|
||||
|
||||
# Run command
|
||||
result = runner.invoke(simulate, [
|
||||
@@ -210,7 +210,7 @@ class TestSimulateCommands:
|
||||
# Patch the hardcoded path
|
||||
with patch('aitbc_cli.commands.simulate.Path') as mock_path_class:
|
||||
mock_path_class.return_value = home_dir
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/home" else Path(x)
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/tests/e2e/fixtures/home" else Path(x)
|
||||
|
||||
# Run command
|
||||
result = runner.invoke(simulate, [
|
||||
@@ -238,7 +238,7 @@ class TestSimulateCommands:
|
||||
# Patch the hardcoded path
|
||||
with patch('aitbc_cli.commands.simulate.Path') as mock_path_class:
|
||||
mock_path_class.return_value = home_dir
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/home" else Path(x)
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/tests/e2e/fixtures/home" else Path(x)
|
||||
|
||||
# Run command
|
||||
result = runner.invoke(simulate, [
|
||||
@@ -347,7 +347,7 @@ class TestSimulateCommands:
|
||||
# Patch the hardcoded path
|
||||
with patch('aitbc_cli.commands.simulate.Path') as mock_path_class:
|
||||
mock_path_class.return_value = home_dir
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/home" else Path(x)
|
||||
mock_path_class.side_effect = lambda x: home_dir if x == "/home/oib/windsurf/aitbc/tests/e2e/fixtures/home" else Path(x)
|
||||
|
||||
# Run command with reset flag
|
||||
result = runner.invoke(simulate, [
|
||||
|
||||
@@ -12,4 +12,66 @@ from aitbc_cli.main import cli
|
||||
|
||||
|
||||
def extract_json_from_output(output):
|
||||
"""Extract JSON from CLI output"""
|
||||
try:
|
||||
# Look for JSON blocks in output
|
||||
json_match = re.search(r'\{.*\}', output, re.DOTALL)
|
||||
if json_match:
|
||||
return json.loads(json_match.group())
|
||||
return None
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
|
||||
|
||||
class TestWalletCommands:
|
||||
"""Test suite for wallet commands"""
|
||||
|
||||
def test_wallet_help(self):
|
||||
"""Test wallet help command"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['wallet', '--help'])
|
||||
assert result.exit_code == 0
|
||||
assert 'wallet' in result.output.lower()
|
||||
|
||||
def test_wallet_create(self):
|
||||
"""Test wallet creation"""
|
||||
runner = CliRunner()
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Set wallet directory in environment
|
||||
env = {'WALLET_DIR': temp_dir}
|
||||
# Use unique wallet name with timestamp
|
||||
import time
|
||||
wallet_name = f"test-wallet-{int(time.time())}"
|
||||
result = runner.invoke(cli, ['wallet', 'create', wallet_name], env=env)
|
||||
print(f"Exit code: {result.exit_code}")
|
||||
print(f"Output: {result.output}")
|
||||
print(f"Temp dir contents: {list(Path(temp_dir).iterdir())}")
|
||||
assert result.exit_code == 0
|
||||
# Check if wallet was created successfully
|
||||
assert 'created' in result.output.lower() or 'wallet' in result.output.lower()
|
||||
|
||||
def test_wallet_balance(self):
|
||||
"""Test wallet balance command"""
|
||||
runner = CliRunner()
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Set wallet directory in environment
|
||||
env = {'WALLET_DIR': temp_dir}
|
||||
# Use unique wallet name
|
||||
import time
|
||||
wallet_name = f"test-wallet-balance-{int(time.time())}"
|
||||
# Create wallet first
|
||||
create_result = runner.invoke(cli, ['wallet', 'create', wallet_name], env=env)
|
||||
assert create_result.exit_code == 0
|
||||
|
||||
# Switch to the created wallet
|
||||
switch_result = runner.invoke(cli, ['wallet', 'switch', wallet_name], env=env)
|
||||
assert switch_result.exit_code == 0
|
||||
|
||||
# Check balance (uses current active wallet)
|
||||
result = runner.invoke(cli, ['wallet', 'balance'], env=env)
|
||||
print(f"Balance exit code: {result.exit_code}")
|
||||
print(f"Balance output: {result.output}")
|
||||
assert result.exit_code == 0
|
||||
# Should contain balance information
|
||||
assert 'balance' in result.output.lower() or 'aitbc' in result.output.lower()
|
||||
|
||||
Reference in New Issue
Block a user