Files
pic/tests/test_cli_tool.py
T
roof 5239751a71 fix: all 214 tests passing (from 36 failures)
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>
2026-04-19 16:43:07 -04:00

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()