test: raise coverage 68.7% -> ~80.4%; add ~250 tests for new egress/DDNS/network paths
Unit Tests / test (push) Successful in 12m6s
Unit Tests / test (push) Successful in 12m6s
Coverage was below acceptable levels and several newly-added code paths (sshuttle egress, proxy egress, DDNS provider stubs, DNS overview route, peer-registry provisioning) had zero test coverage. ~250 new unit tests are added across 16 new test files. Existing test files are updated to match refactored interfaces (DHCP removed, constants introduced, network_manager restructured). .coveragerc is added to pin the source mapping and the 70% floor so regressions are caught at commit time. tests/test_enhanced_api.py was previously living in api/ (wrong location) and is moved to tests/ where it belongs. Integration test files are updated to remove references to DHCP endpoints and add coverage for the new DNS overview and DDNS sync endpoints. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,696 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Additional tests for enhanced_cli.py covering uncovered paths:
|
||||
- EnhancedCLI.do_* methods
|
||||
- EnhancedCLI._display_* methods
|
||||
- EnhancedCLI.show_status, list_services, show_config
|
||||
- EnhancedCLI.batch_start_services, batch_stop_services
|
||||
- APIClient.request (PUT, DELETE branches, error handling)
|
||||
- Module-level: batch_operations, export_config, import_config
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import tempfile
|
||||
import os
|
||||
import shutil
|
||||
import unittest
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock, call
|
||||
|
||||
api_dir = Path(__file__).parent.parent / 'api'
|
||||
sys.path.insert(0, str(api_dir))
|
||||
|
||||
from enhanced_cli import EnhancedCLI, APIClient, ConfigManager, batch_operations, export_config, import_config
|
||||
|
||||
|
||||
class TestAPIClientExtra(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient('http://localhost:3000/api')
|
||||
|
||||
def test_request_put_calls_session_put(self):
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = {'ok': True}
|
||||
mock_resp.raise_for_status.return_value = None
|
||||
with patch.object(self.client.session, 'put', return_value=mock_resp) as mock_put:
|
||||
result = self.client.request('PUT', '/config', {'key': 'val'})
|
||||
mock_put.assert_called_once()
|
||||
self.assertEqual(result, {'ok': True})
|
||||
|
||||
def test_request_delete_calls_session_delete(self):
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = {'deleted': True}
|
||||
mock_resp.raise_for_status.return_value = None
|
||||
with patch.object(self.client.session, 'delete', return_value=mock_resp) as mock_del:
|
||||
result = self.client.request('DELETE', '/peers/alice')
|
||||
mock_del.assert_called_once()
|
||||
self.assertEqual(result, {'deleted': True})
|
||||
|
||||
def test_request_exception_returns_none(self):
|
||||
import requests as _req
|
||||
with patch.object(self.client.session, 'get',
|
||||
side_effect=_req.exceptions.RequestException('timeout')):
|
||||
result = self.client.request('GET', '/status')
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class TestEnhancedCLIDoMethods(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.cli = EnhancedCLI.__new__(EnhancedCLI)
|
||||
self.cli.api_client = MagicMock()
|
||||
self.cli.config_manager = MagicMock()
|
||||
self.cli.current_service = None
|
||||
self.cli.prompt = 'picell> '
|
||||
|
||||
# ── do_status ─────────────────────────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_status_prints_status(self, mock_print):
|
||||
self.cli.api_client.request.return_value = {'cell_name': 'mycel', 'peers_count': 2}
|
||||
self.cli.do_status('')
|
||||
self.assertTrue(any('mycel' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_status_prints_error_when_api_fails(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_status('')
|
||||
mock_print.assert_any_call('❌ Failed to get status')
|
||||
|
||||
# ── do_services ───────────────────────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_services_prints_services(self, mock_print):
|
||||
self.cli.api_client.request.return_value = {
|
||||
'email': {'running': True, 'status': 'online'}}
|
||||
self.cli.do_services('')
|
||||
self.assertTrue(any('email' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_services_prints_error_on_failure(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_services('')
|
||||
mock_print.assert_any_call('❌ Failed to get services status')
|
||||
|
||||
# ── do_peers ──────────────────────────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_peers_empty_list_prints_message(self, mock_print):
|
||||
self.cli.api_client.request.return_value = []
|
||||
self.cli.do_peers('')
|
||||
mock_print.assert_any_call('📭 No peers configured.')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_peers_error_when_none_returned(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_peers('')
|
||||
mock_print.assert_any_call('❌ Failed to fetch peers')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_peers_shows_peer_list(self, mock_print):
|
||||
self.cli.api_client.request.return_value = [
|
||||
{'name': 'alice', 'ip': '10.0.0.2', 'public_key': 'abc123xyz', 'added_at': '2026-01-01'}
|
||||
]
|
||||
self.cli.do_peers('')
|
||||
self.assertTrue(any('alice' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
# ── do_add_peer ───────────────────────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_add_peer_too_few_args(self, mock_print):
|
||||
self.cli.do_add_peer('alice')
|
||||
mock_print.assert_any_call('❌ Usage: add_peer <name> <ip> <public_key>')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_add_peer_success(self, mock_print):
|
||||
self.cli.api_client.request.return_value = {'message': 'Added'}
|
||||
self.cli.do_add_peer('alice 10.0.0.2 abc123key')
|
||||
mock_print.assert_any_call('✅ Added')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_add_peer_failure(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_add_peer('alice 10.0.0.2 abc123key')
|
||||
mock_print.assert_any_call('❌ Failed to add peer')
|
||||
|
||||
# ── do_remove_peer ────────────────────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_remove_peer_no_arg(self, mock_print):
|
||||
self.cli.do_remove_peer('')
|
||||
mock_print.assert_any_call('❌ Usage: remove_peer <name>')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_remove_peer_success(self, mock_print):
|
||||
self.cli.api_client.request.return_value = {'message': 'Removed'}
|
||||
self.cli.do_remove_peer('alice')
|
||||
mock_print.assert_any_call('✅ Removed')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_remove_peer_failure(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_remove_peer('alice')
|
||||
mock_print.assert_any_call('❌ Failed to remove peer')
|
||||
|
||||
# ── do_config ─────────────────────────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_config_shows_config(self, mock_print):
|
||||
self.cli.api_client.request.return_value = {'cell_name': 'mycel'}
|
||||
self.cli.do_config('')
|
||||
self.assertTrue(any('cell_name' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_config_error_on_failure(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_config('')
|
||||
mock_print.assert_any_call('❌ Failed to get configuration')
|
||||
|
||||
# ── do_update_config ──────────────────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_update_config_too_few_args(self, mock_print):
|
||||
self.cli.do_update_config('cell_name')
|
||||
mock_print.assert_any_call('❌ Usage: update_config <key> <value>')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_update_config_success(self, mock_print):
|
||||
self.cli.api_client.request.return_value = {'message': 'Updated'}
|
||||
self.cli.do_update_config('cell_name newcell')
|
||||
mock_print.assert_any_call('✅ Updated')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_update_config_failure(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_update_config('cell_name newcell')
|
||||
mock_print.assert_any_call('❌ Failed to update configuration')
|
||||
|
||||
# ── do_logs ───────────────────────────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_logs_with_log_data(self, mock_print):
|
||||
self.cli.api_client.request.return_value = {'log': 'line1\nline2\n'}
|
||||
self.cli.do_logs('api 10')
|
||||
self.assertTrue(any('line1' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_logs_failure(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_logs('')
|
||||
mock_print.assert_any_call('❌ Failed to get logs')
|
||||
|
||||
# ── do_health ─────────────────────────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_health_shows_history(self, mock_print):
|
||||
self.cli.api_client.request.return_value = [
|
||||
{'timestamp': '2026-01-01T00:00:00', 'alerts': ['disk full']}
|
||||
]
|
||||
self.cli.do_health('')
|
||||
self.assertTrue(any('disk full' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_health_error(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_health('')
|
||||
mock_print.assert_any_call('❌ Failed to get health data')
|
||||
|
||||
# ── do_backup / do_restore / do_backups ───────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_backup_success(self, mock_print):
|
||||
self.cli.api_client.request.return_value = {'backup_id': 'bk123'}
|
||||
self.cli.do_backup('')
|
||||
mock_print.assert_any_call('✅ Backup created: bk123')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_backup_failure(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_backup('')
|
||||
mock_print.assert_any_call('❌ Failed to create backup')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_restore_no_arg(self, mock_print):
|
||||
self.cli.do_restore('')
|
||||
mock_print.assert_any_call('❌ Usage: restore <backup_id>')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_restore_success(self, mock_print):
|
||||
self.cli.api_client.request.return_value = {'ok': True}
|
||||
self.cli.do_restore('bk123')
|
||||
mock_print.assert_any_call('✅ Configuration restored from backup: bk123')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_restore_failure(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_restore('bk123')
|
||||
mock_print.assert_any_call('❌ Failed to restore configuration')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_backups_success(self, mock_print):
|
||||
self.cli.api_client.request.return_value = [
|
||||
{'backup_id': 'bk1', 'timestamp': '2026-01-01', 'services': ['dns']}
|
||||
]
|
||||
self.cli.do_backups('')
|
||||
self.assertTrue(any('bk1' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_backups_failure(self, mock_print):
|
||||
self.cli.api_client.request.return_value = None
|
||||
self.cli.do_backups('')
|
||||
mock_print.assert_any_call('❌ Failed to get backups')
|
||||
|
||||
# ── do_service ────────────────────────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_service_no_arg(self, mock_print):
|
||||
self.cli.do_service('')
|
||||
mock_print.assert_any_call('❌ Usage: service <service_name>')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_service_sets_context(self, mock_print):
|
||||
self.cli.do_service('email')
|
||||
self.assertEqual(self.cli.current_service, 'email')
|
||||
self.assertEqual(self.cli.prompt, 'picell:email> ')
|
||||
|
||||
# ── do_exit / do_quit / do_EOF ───────────────────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_exit_returns_true(self, mock_print):
|
||||
result = self.cli.do_exit('')
|
||||
self.assertTrue(result)
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_quit_delegates_to_exit(self, mock_print):
|
||||
result = self.cli.do_quit('')
|
||||
self.assertTrue(result)
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_do_eof_returns_true(self, mock_print):
|
||||
result = self.cli.do_EOF('')
|
||||
self.assertTrue(result)
|
||||
|
||||
# ── show_status / list_services / show_config ─────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_show_status(self, mock_print):
|
||||
self.cli.api_client.get = MagicMock(return_value={'cell_name': 'mycel', 'peers_count': 1})
|
||||
self.cli.show_status()
|
||||
self.assertTrue(any('mycel' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_show_status_handles_none(self, mock_print):
|
||||
self.cli.api_client.get = MagicMock(return_value=None)
|
||||
self.cli.show_status() # Should not raise
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_list_services(self, mock_print):
|
||||
self.cli.api_client.get = MagicMock(return_value={'email': {'running': True}})
|
||||
self.cli.list_services()
|
||||
mock_print.assert_called_once()
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_show_config(self, mock_print):
|
||||
self.cli.api_client.get = MagicMock(return_value={'cell_name': 'mycel'})
|
||||
self.cli.show_config()
|
||||
self.assertTrue(any('cell_name' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
# ── batch_start_services / batch_stop_services ────────────────────────────
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_batch_start_services(self, mock_print):
|
||||
self.cli.api_client.post = MagicMock(return_value={'ok': True})
|
||||
self.cli.batch_start_services(['email', 'dns'])
|
||||
self.assertEqual(self.cli.api_client.post.call_count, 2)
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_batch_stop_services(self, mock_print):
|
||||
self.cli.api_client.post = MagicMock(return_value={'ok': True})
|
||||
self.cli.batch_stop_services(['email'])
|
||||
self.assertEqual(self.cli.api_client.post.call_count, 1)
|
||||
|
||||
|
||||
class TestDisplayMethods(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.cli = EnhancedCLI.__new__(EnhancedCLI)
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_display_status_with_list_services(self, mock_print):
|
||||
self.cli._display_status({'cell_name': 'mycel', 'services': ['dns', 'dhcp']})
|
||||
self.assertTrue(any('dns' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_display_status_with_dict_services(self, mock_print):
|
||||
self.cli._display_status({
|
||||
'cell_name': 'mycel',
|
||||
'services': {
|
||||
'email': {'running': True, 'status': 'online'},
|
||||
'dns': False # non-dict service
|
||||
}
|
||||
})
|
||||
self.assertTrue(any('email' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_display_services_with_non_dict_status(self, mock_print):
|
||||
self.cli._display_services({'email': True, 'timestamp': '2026-01-01'})
|
||||
self.assertTrue(any('email' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_display_peers(self, mock_print):
|
||||
self.cli._display_peers([
|
||||
{'name': 'alice', 'ip': '10.0.0.2', 'public_key': 'abcdefghijklmnopqrst', 'added_at': '2026-01-01'}
|
||||
])
|
||||
self.assertTrue(any('alice' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_display_health_with_alerts(self, mock_print):
|
||||
self.cli._display_health([
|
||||
{'timestamp': '2026-01-01T00:00:00', 'alerts': ['disk full']}
|
||||
])
|
||||
self.assertTrue(any('disk full' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_display_health_no_alerts(self, mock_print):
|
||||
self.cli._display_health([
|
||||
{'timestamp': '2026-01-01T00:00:00', 'alerts': []}
|
||||
])
|
||||
self.assertTrue(any('2026-01-01' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
|
||||
class TestModuleLevelFunctions(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmp)
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_batch_operations(self, mock_print):
|
||||
"""batch_operations executes commands via EnhancedCLI.onecmd."""
|
||||
# Mock requests to avoid actual HTTP calls
|
||||
with patch('enhanced_cli.requests.get', side_effect=Exception('no server')):
|
||||
batch_operations(['status', 'config'])
|
||||
# Should have printed headers for both commands
|
||||
self.assertTrue(mock_print.call_count >= 2)
|
||||
|
||||
def test_export_config_json(self):
|
||||
with patch('enhanced_cli.ConfigManager.__init__', lambda self, *a, **kw: setattr(self, 'config', {'key': 'val'}) or None):
|
||||
with patch.object(ConfigManager, '_load_config', return_value={'key': 'val'}):
|
||||
result = export_config('json')
|
||||
self.assertIn('key', result)
|
||||
|
||||
def test_import_config_success(self):
|
||||
config_file = os.path.join(self.tmp, 'config.json')
|
||||
with open(config_file, 'w') as f:
|
||||
json.dump({'key': 'val'}, f)
|
||||
# Use real ConfigManager with temp dir
|
||||
with patch('enhanced_cli.ConfigManager') as MockCM:
|
||||
mock_instance = MagicMock()
|
||||
MockCM.return_value = mock_instance
|
||||
result = import_config(config_file, 'json')
|
||||
self.assertTrue(result)
|
||||
mock_instance.import_config.assert_called_once()
|
||||
|
||||
def test_import_config_nonexistent_file_returns_false(self):
|
||||
result = import_config('/nonexistent/config.json', 'json')
|
||||
self.assertFalse(result)
|
||||
|
||||
|
||||
class TestConfigManagerJsonPath(unittest.TestCase):
|
||||
"""Cover ConfigManager branches that use .json suffix."""
|
||||
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmp)
|
||||
|
||||
def test_init_with_json_path_sets_config_file_directly(self):
|
||||
p = os.path.join(self.tmp, 'cfg.json')
|
||||
cm = ConfigManager(p)
|
||||
self.assertEqual(str(cm.config_file), p)
|
||||
self.assertEqual(cm.config_dir, cm.config_file.parent)
|
||||
|
||||
def test_load_config_reads_json_file(self):
|
||||
p = os.path.join(self.tmp, 'cfg.json')
|
||||
with open(p, 'w') as f:
|
||||
json.dump({'hello': 'world'}, f)
|
||||
cm = ConfigManager(p)
|
||||
self.assertEqual(cm.config.get('hello'), 'world')
|
||||
|
||||
def test_load_config_exception_returns_empty(self):
|
||||
p = os.path.join(self.tmp, 'cfg.json')
|
||||
# Write invalid JSON
|
||||
with open(p, 'w') as f:
|
||||
f.write('not json {{')
|
||||
cm = ConfigManager(p)
|
||||
self.assertEqual(cm.config, {})
|
||||
|
||||
def test_save_config_writes_json(self):
|
||||
p = os.path.join(self.tmp, 'cfg.json')
|
||||
cm = ConfigManager(p)
|
||||
cm.config = {'saved': True}
|
||||
cm.save()
|
||||
with open(p) as f:
|
||||
data = json.load(f)
|
||||
self.assertTrue(data.get('saved'))
|
||||
|
||||
def test_save_config_exception_does_not_raise(self):
|
||||
p = os.path.join(self.tmp, 'cfg.json')
|
||||
cm = ConfigManager(p)
|
||||
# Make the file unwritable by mocking open to raise
|
||||
with patch('builtins.open', side_effect=OSError('disk full')):
|
||||
cm.save() # must not raise
|
||||
|
||||
def test_export_config_yaml_format(self):
|
||||
p = os.path.join(self.tmp, 'cfg.json')
|
||||
cm = ConfigManager(p)
|
||||
cm.config = {'key': 'value'}
|
||||
result = cm.export_config('yaml')
|
||||
self.assertIn('key', result)
|
||||
|
||||
def test_export_config_unsupported_format_raises(self):
|
||||
p = os.path.join(self.tmp, 'cfg.json')
|
||||
cm = ConfigManager(p)
|
||||
with self.assertRaises(ValueError):
|
||||
cm.export_config('xml')
|
||||
|
||||
def test_import_config_yaml(self):
|
||||
p = os.path.join(self.tmp, 'cfg.json')
|
||||
cm = ConfigManager(p)
|
||||
cm.import_config('key: value\n', 'yaml')
|
||||
self.assertEqual(cm.config.get('key'), 'value')
|
||||
|
||||
def test_import_config_unsupported_format_prints_error(self):
|
||||
p = os.path.join(self.tmp, 'cfg.json')
|
||||
cm = ConfigManager(p)
|
||||
# Should not raise even with unsupported format (caught internally)
|
||||
with patch('builtins.print') as mock_print:
|
||||
cm.import_config('...', 'xml')
|
||||
self.assertTrue(any('Error' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
|
||||
class TestEnhancedCLIGetPost(unittest.TestCase):
|
||||
"""Cover EnhancedCLI.get() and .post() HTTP shortcut methods."""
|
||||
|
||||
def setUp(self):
|
||||
self.cli = EnhancedCLI.__new__(EnhancedCLI)
|
||||
self.cli.api_client = MagicMock()
|
||||
self.cli.api_client.base_url = 'http://localhost:3000/api'
|
||||
|
||||
@patch('enhanced_cli.requests.get')
|
||||
def test_get_returns_json_on_success(self, mock_get):
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = {'ok': True}
|
||||
mock_resp.raise_for_status.return_value = None
|
||||
mock_get.return_value = mock_resp
|
||||
result = self.cli.get('/status')
|
||||
self.assertEqual(result, {'ok': True})
|
||||
|
||||
@patch('enhanced_cli.requests.get')
|
||||
def test_get_returns_none_on_exception(self, mock_get):
|
||||
mock_get.side_effect = Exception('connection refused')
|
||||
result = self.cli.get('/status')
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch('enhanced_cli.requests.post')
|
||||
def test_post_returns_json_on_success(self, mock_post):
|
||||
mock_resp = MagicMock()
|
||||
mock_resp.json.return_value = {'created': True}
|
||||
mock_resp.raise_for_status.return_value = None
|
||||
mock_post.return_value = mock_resp
|
||||
result = self.cli.post('/peers', {'name': 'alice'})
|
||||
self.assertEqual(result, {'created': True})
|
||||
|
||||
@patch('enhanced_cli.requests.post')
|
||||
def test_post_returns_none_on_exception(self, mock_post):
|
||||
mock_post.side_effect = Exception('timeout')
|
||||
result = self.cli.post('/peers', {'name': 'alice'})
|
||||
self.assertIsNone(result)
|
||||
|
||||
|
||||
class TestShowStatusExceptionPath(unittest.TestCase):
|
||||
"""Cover show_status() exception branch."""
|
||||
|
||||
def setUp(self):
|
||||
self.cli = EnhancedCLI.__new__(EnhancedCLI)
|
||||
self.cli.api_client = MagicMock()
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_show_status_exception_prints_error(self, mock_print):
|
||||
self.cli.api_client.get.side_effect = RuntimeError('api down')
|
||||
self.cli.show_status()
|
||||
self.assertTrue(any('Error' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
|
||||
class TestInteractiveMode(unittest.TestCase):
|
||||
"""Cover interactive_mode() loop."""
|
||||
|
||||
def setUp(self):
|
||||
self.cli = EnhancedCLI.__new__(EnhancedCLI)
|
||||
self.cli.api_client = MagicMock()
|
||||
self.cli.config_manager = MagicMock()
|
||||
self.cli.current_service = None
|
||||
self.cli.prompt = 'picell> '
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_interactive_mode_exits_on_quit(self, mock_print):
|
||||
with patch('builtins.input', side_effect=['quit']):
|
||||
self.cli.interactive_mode()
|
||||
mock_print.assert_any_call('Entering interactive mode. Type \'quit\' to exit.')
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_interactive_mode_exits_on_eof(self, mock_print):
|
||||
with patch('builtins.input', side_effect=EOFError):
|
||||
self.cli.interactive_mode()
|
||||
|
||||
|
||||
class TestMainFunction(unittest.TestCase):
|
||||
"""Cover main() argument branches."""
|
||||
|
||||
def _run_main(self, args):
|
||||
import sys as _sys
|
||||
old_argv = _sys.argv
|
||||
_sys.argv = ['enhanced_cli'] + args
|
||||
try:
|
||||
from enhanced_cli import main
|
||||
with patch('builtins.print'):
|
||||
try:
|
||||
main()
|
||||
except SystemExit:
|
||||
pass
|
||||
finally:
|
||||
_sys.argv = old_argv
|
||||
|
||||
def test_main_no_args_prints_help(self):
|
||||
with patch('enhanced_cli.argparse.ArgumentParser.print_help') as mock_help:
|
||||
self._run_main([])
|
||||
mock_help.assert_called_once()
|
||||
|
||||
def test_main_status_flag(self):
|
||||
with patch('enhanced_cli.EnhancedCLI') as MockCLI:
|
||||
mock_cli = MagicMock()
|
||||
MockCLI.return_value = mock_cli
|
||||
self._run_main(['--status'])
|
||||
mock_cli.do_status.assert_called_once_with('')
|
||||
|
||||
def test_main_services_flag(self):
|
||||
with patch('enhanced_cli.EnhancedCLI') as MockCLI:
|
||||
mock_cli = MagicMock()
|
||||
MockCLI.return_value = mock_cli
|
||||
self._run_main(['--services'])
|
||||
mock_cli.do_services.assert_called_once_with('')
|
||||
|
||||
def test_main_peers_flag(self):
|
||||
with patch('enhanced_cli.EnhancedCLI') as MockCLI:
|
||||
mock_cli = MagicMock()
|
||||
MockCLI.return_value = mock_cli
|
||||
self._run_main(['--peers'])
|
||||
mock_cli.do_peers.assert_called_once_with('')
|
||||
|
||||
def test_main_logs_flag(self):
|
||||
with patch('enhanced_cli.EnhancedCLI') as MockCLI:
|
||||
mock_cli = MagicMock()
|
||||
MockCLI.return_value = mock_cli
|
||||
self._run_main(['--logs', 'api'])
|
||||
mock_cli.do_logs.assert_called_once_with('api')
|
||||
|
||||
def test_main_health_flag(self):
|
||||
with patch('enhanced_cli.EnhancedCLI') as MockCLI:
|
||||
mock_cli = MagicMock()
|
||||
MockCLI.return_value = mock_cli
|
||||
self._run_main(['--health'])
|
||||
mock_cli.do_health.assert_called_once_with('')
|
||||
|
||||
def test_main_batch_flag(self):
|
||||
with patch('enhanced_cli.batch_operations') as mock_batch:
|
||||
self._run_main(['--batch', 'status', 'config'])
|
||||
mock_batch.assert_called_once_with(['status', 'config'])
|
||||
|
||||
def test_main_export_config_flag(self):
|
||||
with patch('enhanced_cli.export_config', return_value='{}') as mock_export:
|
||||
self._run_main(['--export-config', 'json'])
|
||||
mock_export.assert_called_once_with('json')
|
||||
|
||||
def test_main_import_config_json_file(self):
|
||||
with patch('enhanced_cli.import_config', return_value=True) as mock_import:
|
||||
self._run_main(['--import-config', 'config.json'])
|
||||
mock_import.assert_called_once_with('config.json', 'json')
|
||||
|
||||
def test_main_import_config_yaml_file(self):
|
||||
with patch('enhanced_cli.import_config', return_value=True) as mock_import:
|
||||
self._run_main(['--import-config', 'config.yaml'])
|
||||
mock_import.assert_called_once_with('config.yaml', 'yaml')
|
||||
|
||||
def test_main_wizard_flag(self):
|
||||
with patch('enhanced_cli.service_wizard') as mock_wizard:
|
||||
self._run_main(['--wizard', 'email'])
|
||||
mock_wizard.assert_called_once_with('email')
|
||||
|
||||
|
||||
class TestServiceWizardFunction(unittest.TestCase):
|
||||
"""Cover service_wizard() branches."""
|
||||
|
||||
def _call_wizard(self, service, inputs):
|
||||
from enhanced_cli import service_wizard
|
||||
with patch('builtins.input', side_effect=inputs):
|
||||
with patch('builtins.print'):
|
||||
with patch('enhanced_cli.APIClient') as MockAPI:
|
||||
mock_client = MagicMock()
|
||||
mock_client.request.return_value = {'ok': True}
|
||||
MockAPI.return_value = mock_client
|
||||
service_wizard(service)
|
||||
return mock_client
|
||||
|
||||
def test_service_wizard_network_calls_api(self):
|
||||
client = self._call_wizard('network', ['53', '10.0.0.100-200', '', ''])
|
||||
client.request.assert_called_once()
|
||||
|
||||
def test_service_wizard_wireguard_calls_api(self):
|
||||
client = self._call_wizard('wireguard', ['51820', '10.0.0.1/24'])
|
||||
client.request.assert_called_once()
|
||||
|
||||
def test_service_wizard_email_calls_api(self):
|
||||
client = self._call_wizard('email', ['example.com', '587', '993'])
|
||||
client.request.assert_called_once()
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_service_wizard_unknown_service_prints_error(self, mock_print):
|
||||
from enhanced_cli import service_wizard
|
||||
service_wizard('unknown_service')
|
||||
self.assertTrue(any('Wizard not available' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
@patch('builtins.print')
|
||||
def test_service_wizard_api_failure_prints_error(self, mock_print):
|
||||
from enhanced_cli import service_wizard
|
||||
with patch('builtins.input', side_effect=['53', '10.0.0.100-200', '', '']):
|
||||
with patch('enhanced_cli.APIClient') as MockAPI:
|
||||
mock_client = MagicMock()
|
||||
mock_client.request.return_value = None
|
||||
MockAPI.return_value = mock_client
|
||||
service_wizard('network')
|
||||
self.assertTrue(any('Failed' in str(c) for c in mock_print.call_args_list))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user