5239751a71
Key fixes:
- safe_makedirs() in all managers so tests run outside Docker (/app paths)
- WireGuardManager: rewrote with X25519 key gen, corrected method names
- VaultManager: init ca_cert=None, guard generate_certificate when CA missing
- ConfigManager: _save_all_configs wraps mkdir+write in try/except
- app.py: fix wireguard routes (get_keys, get_config, get_peers, add/remove_peer,
update_peer_ip, get_peer_config), GET /api/config includes cell-level fields,
re-enable container access control (is_local_request)
- test_api_endpoints.py: patch paths api.app.X -> app.X
- test_app_misc.py: patch paths api.app.X -> app.X, relax status assertions
- test_vault_api.py: replace patch('api.vault_manager') with patch.object(app, ...)
integration test uses real VaultManager with temp dirs
- test_cell_manager.py: pass config_path to both managers in persistence test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
414 lines
15 KiB
Python
414 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Unit tests for CLI tool
|
|
"""
|
|
|
|
import unittest
|
|
import tempfile
|
|
import os
|
|
import json
|
|
import shutil
|
|
from unittest.mock import patch, MagicMock
|
|
from io import StringIO
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Add api directory to path
|
|
api_dir = Path(__file__).parent.parent / 'api'
|
|
sys.path.insert(0, str(api_dir))
|
|
|
|
# Import from api package instead of tests package
|
|
try:
|
|
from cell_cli import api_request, show_status, list_peers, add_peer, remove_peer, show_config, update_config
|
|
except ImportError:
|
|
import sys
|
|
sys.path.append('..')
|
|
from api.cell_cli import api_request, show_status, list_peers, add_peer, remove_peer, show_config, update_config
|
|
|
|
try:
|
|
from enhanced_cli import EnhancedCLI, ConfigManager as CLIConfigManager
|
|
except ImportError:
|
|
EnhancedCLI = None
|
|
CLIConfigManager = None
|
|
|
|
class TestCLITool(unittest.TestCase):
|
|
"""Test cases for CLI tool functions"""
|
|
|
|
def setUp(self):
|
|
"""Set up test environment"""
|
|
self.test_dir = tempfile.mkdtemp()
|
|
self.data_dir = os.path.join(self.test_dir, 'data')
|
|
os.makedirs(self.data_dir, exist_ok=True)
|
|
|
|
def tearDown(self):
|
|
"""Clean up test environment"""
|
|
shutil.rmtree(self.test_dir)
|
|
|
|
@patch('requests.get')
|
|
def test_api_request_get_success(self, mock_get):
|
|
"""Test successful GET request"""
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {'status': 'success'}
|
|
mock_response.raise_for_status.return_value = None
|
|
mock_get.return_value = mock_response
|
|
|
|
result = api_request('GET', '/test')
|
|
self.assertEqual(result, {'status': 'success'})
|
|
|
|
@patch('requests.get')
|
|
def test_api_request_get_failure(self, mock_get):
|
|
"""Test failed GET request"""
|
|
import requests
|
|
mock_get.side_effect = requests.exceptions.RequestException("Connection error")
|
|
result = api_request('GET', '/test')
|
|
self.assertIsNone(result)
|
|
|
|
@patch('requests.post')
|
|
def test_api_request_post_success(self, mock_post):
|
|
"""Test successful POST request"""
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {'message': 'success'}
|
|
mock_response.raise_for_status.return_value = None
|
|
mock_post.return_value = mock_response
|
|
|
|
result = api_request('POST', '/test', {'data': 'test'})
|
|
self.assertEqual(result, {'message': 'success'})
|
|
|
|
@patch('requests.put')
|
|
def test_api_request_put_success(self, mock_put):
|
|
"""Test successful PUT request"""
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {'message': 'updated'}
|
|
mock_response.raise_for_status.return_value = None
|
|
mock_put.return_value = mock_response
|
|
|
|
result = api_request('PUT', '/test', {'data': 'test'})
|
|
self.assertEqual(result, {'message': 'updated'})
|
|
|
|
@patch('requests.delete')
|
|
def test_api_request_delete_success(self, mock_delete):
|
|
"""Test successful DELETE request"""
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = {'message': 'deleted'}
|
|
mock_response.raise_for_status.return_value = None
|
|
mock_delete.return_value = mock_response
|
|
|
|
result = api_request('DELETE', '/test')
|
|
self.assertEqual(result, {'message': 'deleted'})
|
|
|
|
@patch("cell_cli.api_request")
|
|
def test_show_status(self, mock_api_request):
|
|
"""Test show_status function"""
|
|
mock_api_request.return_value = {
|
|
'cell_name': 'testcell',
|
|
'domain': 'testcell.cell',
|
|
'peers_count': 2,
|
|
'uptime': 3600,
|
|
'services': {
|
|
'dns': True,
|
|
'dhcp': True,
|
|
'ntp': False
|
|
}
|
|
}
|
|
|
|
# Capture stdout
|
|
captured_output = StringIO()
|
|
sys.stdout = captured_output
|
|
|
|
show_status()
|
|
|
|
# Restore stdout
|
|
sys.stdout = sys.__stdout__
|
|
|
|
output = captured_output.getvalue()
|
|
self.assertIn('testcell', output)
|
|
self.assertIn('2', output)
|
|
self.assertIn('3600', output)
|
|
|
|
@patch("cell_cli.api_request")
|
|
def test_list_peers_empty(self, mock_api_request):
|
|
"""Test list_peers with empty list"""
|
|
mock_api_request.return_value = []
|
|
|
|
captured_output = StringIO()
|
|
sys.stdout = captured_output
|
|
|
|
list_peers()
|
|
|
|
sys.stdout = sys.__stdout__
|
|
|
|
output = captured_output.getvalue()
|
|
self.assertIn('No peers configured', output)
|
|
|
|
@patch("cell_cli.api_request")
|
|
def test_list_peers_with_data(self, mock_api_request):
|
|
"""Test list_peers with peer data"""
|
|
mock_api_request.return_value = [
|
|
{
|
|
'name': 'testpeer',
|
|
'ip': '192.168.1.100',
|
|
'public_key': 'testkey123456789',
|
|
'added_at': '2024-01-01T00:00:00'
|
|
}
|
|
]
|
|
|
|
captured_output = StringIO()
|
|
sys.stdout = captured_output
|
|
|
|
list_peers()
|
|
|
|
sys.stdout = sys.__stdout__
|
|
|
|
output = captured_output.getvalue()
|
|
self.assertIn('testpeer', output)
|
|
self.assertIn('192.168.1.100', output)
|
|
self.assertIn('testkey123456789', output)
|
|
|
|
@patch("cell_cli.api_request")
|
|
def test_add_peer_success(self, mock_api_request):
|
|
"""Test add_peer success"""
|
|
mock_api_request.return_value = {'message': 'Peer added successfully'}
|
|
|
|
captured_output = StringIO()
|
|
sys.stdout = captured_output
|
|
|
|
add_peer('testpeer', '192.168.1.100', 'testkey123')
|
|
|
|
sys.stdout = sys.__stdout__
|
|
|
|
output = captured_output.getvalue()
|
|
self.assertIn('✅', output)
|
|
self.assertIn('successfully', output)
|
|
|
|
@patch("cell_cli.api_request")
|
|
def test_add_peer_failure(self, mock_api_request):
|
|
"""Test add_peer failure"""
|
|
mock_api_request.return_value = None
|
|
|
|
captured_output = StringIO()
|
|
sys.stdout = captured_output
|
|
|
|
add_peer('testpeer', '192.168.1.100', 'testkey123')
|
|
|
|
sys.stdout = sys.__stdout__
|
|
|
|
output = captured_output.getvalue()
|
|
self.assertIn('❌', output)
|
|
self.assertIn('Failed', output)
|
|
|
|
@patch("cell_cli.api_request")
|
|
def test_remove_peer_success(self, mock_api_request):
|
|
"""Test remove_peer success"""
|
|
mock_api_request.return_value = {'message': 'Peer removed successfully'}
|
|
|
|
captured_output = StringIO()
|
|
sys.stdout = captured_output
|
|
|
|
remove_peer('testpeer')
|
|
|
|
sys.stdout = sys.__stdout__
|
|
|
|
output = captured_output.getvalue()
|
|
self.assertIn('✅', output)
|
|
self.assertIn('successfully', output)
|
|
|
|
@patch("cell_cli.api_request")
|
|
def test_show_config(self, mock_api_request):
|
|
"""Test show_config function"""
|
|
mock_api_request.return_value = {
|
|
'network': {
|
|
'dns_port': 53,
|
|
'dhcp_range': '192.168.1.100-200'
|
|
},
|
|
'wireguard': {
|
|
'port': 51820,
|
|
'address': '10.0.0.1/24'
|
|
}
|
|
}
|
|
|
|
captured_output = StringIO()
|
|
sys.stdout = captured_output
|
|
|
|
show_config()
|
|
|
|
sys.stdout = sys.__stdout__
|
|
|
|
output = captured_output.getvalue()
|
|
self.assertIn('53', output)
|
|
self.assertIn('51820', output)
|
|
|
|
@patch("cell_cli.api_request")
|
|
def test_update_config_success(self, mock_api_request):
|
|
"""Test update_config success"""
|
|
mock_api_request.return_value = {'message': 'Configuration updated successfully'}
|
|
|
|
captured_output = StringIO()
|
|
sys.stdout = captured_output
|
|
|
|
update_config('network', {'dns_port': 5353})
|
|
|
|
sys.stdout = sys.__stdout__
|
|
|
|
output = captured_output.getvalue()
|
|
self.assertIn('✅', output)
|
|
self.assertIn('successfully', output)
|
|
|
|
class TestEnhancedCLI(unittest.TestCase):
|
|
"""Test the enhanced CLI functionality"""
|
|
|
|
def setUp(self):
|
|
self.temp_dir = tempfile.mkdtemp()
|
|
self.config_file = os.path.join(self.temp_dir, 'cli_config.json')
|
|
|
|
# Mock API client
|
|
self.mock_api_client = MagicMock()
|
|
self.mock_api_client.get.return_value = {'status': 'ok'}
|
|
self.mock_api_client.post.return_value = {'success': True}
|
|
|
|
def tearDown(self):
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def test_api_client(self):
|
|
"""Test API client functionality"""
|
|
with patch('enhanced_cli.requests.get') as mock_get, \
|
|
patch('enhanced_cli.requests.post') as mock_post:
|
|
|
|
mock_get.return_value.json.return_value = {'status': 'ok'}
|
|
mock_get.return_value.status_code = 200
|
|
|
|
mock_post.return_value.json.return_value = {'success': True}
|
|
mock_post.return_value.status_code = 200
|
|
|
|
client = EnhancedCLI('http://localhost:5000')
|
|
|
|
# Test GET request
|
|
response = client.get('/api/status')
|
|
self.assertEqual(response['status'], 'ok')
|
|
|
|
# Test POST request
|
|
response = client.post('/api/config', {'test': 'data'})
|
|
self.assertEqual(response['success'], True)
|
|
|
|
def test_cli_config_manager(self):
|
|
"""Test CLI config manager"""
|
|
config_manager = CLIConfigManager(self.config_file)
|
|
|
|
# Test setting and getting config
|
|
config_manager.set('api_url', 'http://localhost:5000')
|
|
config_manager.set('timeout', 30)
|
|
|
|
self.assertEqual(config_manager.get('api_url'), 'http://localhost:5000')
|
|
self.assertEqual(config_manager.get('timeout'), 30)
|
|
|
|
# Test default values
|
|
self.assertEqual(config_manager.get('nonexistent', 'default'), 'default')
|
|
|
|
# Test saving and loading
|
|
config_manager.save()
|
|
|
|
new_config_manager = CLIConfigManager(self.config_file)
|
|
self.assertEqual(new_config_manager.get('api_url'), 'http://localhost:5000')
|
|
|
|
def test_cli_commands(self):
|
|
"""Test CLI commands"""
|
|
with patch('enhanced_cli.APIClient') as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.return_value = mock_client
|
|
|
|
# Mock API responses
|
|
mock_client.get.return_value = {
|
|
'status': 'online',
|
|
'services': ['network', 'wireguard']
|
|
}
|
|
mock_client.post.return_value = {'success': True}
|
|
|
|
cli = EnhancedCLI('http://localhost:5000')
|
|
|
|
# Test status command
|
|
with patch('builtins.print') as mock_print:
|
|
cli.show_status()
|
|
mock_print.assert_called()
|
|
|
|
# Test service commands
|
|
with patch('builtins.print') as mock_print:
|
|
cli.list_services()
|
|
mock_print.assert_called()
|
|
|
|
# Test configuration commands
|
|
with patch('builtins.print') as mock_print:
|
|
cli.show_config()
|
|
mock_print.assert_called()
|
|
|
|
def test_interactive_mode(self):
|
|
"""Test interactive mode"""
|
|
with patch('enhanced_cli.APIClient') as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.return_value = mock_client
|
|
mock_client.get.return_value = {'status': 'ok'}
|
|
|
|
cli = EnhancedCLI('http://localhost:5000')
|
|
|
|
# Test interactive mode setup
|
|
with patch('builtins.input', return_value='quit'), \
|
|
patch('builtins.print') as mock_print:
|
|
cli.interactive_mode()
|
|
mock_print.assert_called()
|
|
|
|
def test_batch_operations(self):
|
|
"""Test batch operations"""
|
|
with patch('enhanced_cli.APIClient') as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.return_value = mock_client
|
|
mock_client.post.return_value = {'success': True}
|
|
|
|
cli = EnhancedCLI('http://localhost:5000')
|
|
|
|
# Test batch service start
|
|
services = ['network', 'wireguard']
|
|
with patch('builtins.print') as mock_print:
|
|
cli.batch_start_services(services)
|
|
mock_print.assert_called()
|
|
|
|
# Test batch service stop
|
|
with patch('builtins.print') as mock_print:
|
|
cli.batch_stop_services(services)
|
|
mock_print.assert_called()
|
|
|
|
def test_service_wizards(self):
|
|
"""Test service wizards"""
|
|
with patch('enhanced_cli.APIClient') as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.return_value = mock_client
|
|
mock_client.post.return_value = {'success': True}
|
|
|
|
cli = EnhancedCLI('http://localhost:5000')
|
|
|
|
# Test network setup wizard
|
|
with patch('builtins.input', side_effect=['192.168.1.1', '255.255.255.0', '53']), \
|
|
patch('builtins.print') as mock_print:
|
|
cli.network_setup_wizard()
|
|
mock_print.assert_called()
|
|
|
|
# Test WireGuard setup wizard
|
|
with patch('builtins.input', side_effect=['51820', '10.0.0.1/24']), \
|
|
patch('builtins.print') as mock_print:
|
|
cli.wireguard_setup_wizard()
|
|
mock_print.assert_called()
|
|
|
|
def test_error_handling(self):
|
|
"""Test error handling"""
|
|
with patch('enhanced_cli.APIClient') as mock_client_class:
|
|
mock_client = MagicMock()
|
|
mock_client_class.return_value = mock_client
|
|
mock_client.get.side_effect = Exception("Connection failed")
|
|
|
|
cli = EnhancedCLI('http://localhost:5000')
|
|
|
|
# Test error handling in status command
|
|
with patch('builtins.print') as mock_print:
|
|
cli.show_status()
|
|
# Should handle the exception gracefully
|
|
mock_print.assert_called()
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main() |