Files
pic/tests/test_cell_cli_extra.py
T
roof aa1e5c41ec
Unit Tests / test (push) Successful in 12m6s
test: raise coverage 68.7% -> ~80.4%; add ~250 tests for new egress/DDNS/network paths
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>
2026-06-10 09:03:39 -04:00

391 lines
16 KiB
Python

#!/usr/bin/env python3
"""
Additional tests for cell_cli.py covering the functions NOT in test_cli_tool.py:
- list_peers (error path)
- list_nat_rules / add_nat_rule / delete_nat_rule
- list_peer_routes / add_peer_route / delete_peer_route
- list_firewall_rules / add_firewall_rule / delete_firewall_rule
- show_services_status
- list_wireguard_peers
- show_network_info / show_dns_status / show_ntp_status
- main() command routing
"""
import sys
import unittest
from pathlib import Path
from unittest.mock import patch, MagicMock
api_dir = Path(__file__).parent.parent / 'api'
sys.path.insert(0, str(api_dir))
from cell_cli import (
list_peers, add_peer, remove_peer, show_config, update_config,
list_nat_rules, add_nat_rule, delete_nat_rule,
list_peer_routes, add_peer_route, delete_peer_route,
list_firewall_rules, add_firewall_rule, delete_firewall_rule,
show_services_status, list_wireguard_peers,
show_network_info, show_dns_status, show_ntp_status,
)
class TestListPeersErrorPath(unittest.TestCase):
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_list_peers_failure_prints_error(self, mock_req, mock_print):
list_peers()
mock_print.assert_any_call('Failed to fetch peers.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=[])
def test_list_peers_empty_list(self, mock_req, mock_print):
list_peers()
mock_print.assert_any_call('No peers configured.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=[
{'name': 'alice', 'ip': '10.0.0.2',
'public_key': 'abcdefghijklmnopqrstuvwxyz', 'added_at': '2026-01-01'}
])
def test_list_peers_shows_peer_info(self, mock_req, mock_print):
list_peers()
self.assertTrue(any('alice' in str(c) for c in mock_print.call_args_list))
class TestNatRules(unittest.TestCase):
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'nat_rules': []})
def test_list_nat_rules_empty(self, mock_req, mock_print):
list_nat_rules()
mock_print.assert_any_call('No NAT rules configured.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'nat_rules': [
{'id': 1, 'source_network': '10.0.0.0/24', 'target_interface': 'eth0',
'masquerade': True, 'nat_type': 'MASQUERADE', 'protocol': 'ALL',
'external_port': '', 'internal_ip': '', 'internal_port': ''}
]})
def test_list_nat_rules_shows_rules(self, mock_req, mock_print):
list_nat_rules()
self.assertTrue(any('eth0' in str(c) for c in mock_print.call_args_list))
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_list_nat_rules_failure(self, mock_req, mock_print):
list_nat_rules()
mock_print.assert_any_call('Failed to fetch NAT rules.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'id': 1})
def test_add_nat_rule_success(self, mock_req, mock_print):
add_nat_rule('10.0.0.0/24', 'eth0', True, 'MASQUERADE', 'ALL', '', '', '')
mock_print.assert_any_call('✅ NAT rule added.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_add_nat_rule_failure(self, mock_req, mock_print):
add_nat_rule('10.0.0.0/24', 'eth0', False, 'DNAT', 'TCP', '80', '10.0.0.5', '8080')
mock_print.assert_any_call('❌ Failed to add NAT rule.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'ok': True})
def test_delete_nat_rule_success(self, mock_req, mock_print):
delete_nat_rule(1)
mock_print.assert_any_call('✅ NAT rule deleted.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_delete_nat_rule_failure(self, mock_req, mock_print):
delete_nat_rule(99)
mock_print.assert_any_call('❌ Failed to delete NAT rule.')
class TestPeerRoutes(unittest.TestCase):
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'peer_routes': []})
def test_list_peer_routes_empty(self, mock_req, mock_print):
list_peer_routes()
mock_print.assert_any_call('No peer routes configured.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'peer_routes': [
{'peer_name': 'alice', 'peer_ip': '10.0.0.2',
'allowed_networks': ['192.168.1.0/24'], 'route_type': 'split'}
]})
def test_list_peer_routes_shows_routes(self, mock_req, mock_print):
list_peer_routes()
self.assertTrue(any('alice' in str(c) for c in mock_print.call_args_list))
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_list_peer_routes_failure(self, mock_req, mock_print):
list_peer_routes()
mock_print.assert_any_call('Failed to fetch peer routes.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'ok': True})
def test_add_peer_route_success(self, mock_req, mock_print):
add_peer_route('alice', '10.0.0.2', '192.168.1.0/24', 'split')
mock_print.assert_any_call('✅ Peer route added.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_add_peer_route_failure(self, mock_req, mock_print):
add_peer_route('alice', '10.0.0.2', '192.168.1.0/24', 'split')
mock_print.assert_any_call('❌ Failed to add peer route.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'ok': True})
def test_delete_peer_route_success(self, mock_req, mock_print):
delete_peer_route('alice')
mock_print.assert_any_call('✅ Peer route deleted.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_delete_peer_route_failure(self, mock_req, mock_print):
delete_peer_route('alice')
mock_print.assert_any_call('❌ Failed to delete peer route.')
class TestFirewallRules(unittest.TestCase):
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'firewall_rules': []})
def test_list_firewall_rules_empty(self, mock_req, mock_print):
list_firewall_rules()
mock_print.assert_any_call('No firewall rules configured.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'firewall_rules': [
{'id': 1, 'rule_type': 'ACCEPT', 'source': '10.0.0.0/24',
'destination': 'any', 'protocol': 'TCP', 'port_range': '80', 'action': 'ACCEPT'}
]})
def test_list_firewall_rules_shows_rules(self, mock_req, mock_print):
list_firewall_rules()
self.assertTrue(any('ACCEPT' in str(c) for c in mock_print.call_args_list))
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_list_firewall_rules_failure(self, mock_req, mock_print):
list_firewall_rules()
mock_print.assert_any_call('Failed to fetch firewall rules.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'id': 1})
def test_add_firewall_rule_success(self, mock_req, mock_print):
add_firewall_rule('ACCEPT', '10.0.0.0/24', 'any', 'ACCEPT', 'TCP', '80')
mock_print.assert_any_call('✅ Firewall rule added.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_add_firewall_rule_failure(self, mock_req, mock_print):
add_firewall_rule('DROP', 'any', 'any', 'DROP', 'ALL', '')
mock_print.assert_any_call('❌ Failed to add firewall rule.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'ok': True})
def test_delete_firewall_rule_success(self, mock_req, mock_print):
delete_firewall_rule(1)
mock_print.assert_any_call('✅ Firewall rule deleted.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_delete_firewall_rule_failure(self, mock_req, mock_print):
delete_firewall_rule(99)
mock_print.assert_any_call('❌ Failed to delete firewall rule.')
class TestShowServicesStatus(unittest.TestCase):
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={
'email': {'status': 'online', 'running': True},
'dns': True
})
def test_show_services_status_with_dict_and_bool(self, mock_req, mock_print):
show_services_status()
self.assertTrue(any('email' in str(c) for c in mock_print.call_args_list))
self.assertTrue(any('dns' in str(c) for c in mock_print.call_args_list))
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_show_services_status_failure(self, mock_req, mock_print):
show_services_status()
mock_print.assert_any_call('Failed to fetch service status.')
class TestListWireguardPeers(unittest.TestCase):
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=[
{'name': 'alice', 'public_key': 'pk1', 'ip': '10.0.0.2', 'status': 'active'}
])
def test_list_wireguard_peers_shows_peers(self, mock_req, mock_print):
list_wireguard_peers()
self.assertTrue(any('alice' in str(c) for c in mock_print.call_args_list))
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_list_wireguard_peers_failure(self, mock_req, mock_print):
list_wireguard_peers()
mock_print.assert_any_call('Failed to fetch WireGuard peers.')
class TestNetworkDnsNtpStatus(unittest.TestCase):
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'gateway': '192.168.1.1', 'subnet': '10.0.0.0/24'})
def test_show_network_info_success(self, mock_req, mock_print):
show_network_info()
self.assertTrue(any('gateway' in str(c) for c in mock_print.call_args_list))
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_show_network_info_failure(self, mock_req, mock_print):
show_network_info()
mock_print.assert_any_call('Failed to fetch network info.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'running': True, 'port': 53})
def test_show_dns_status_success(self, mock_req, mock_print):
show_dns_status()
self.assertTrue(any('running' in str(c) for c in mock_print.call_args_list))
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_show_dns_status_failure(self, mock_req, mock_print):
show_dns_status()
mock_print.assert_any_call('Failed to fetch DNS status.')
@patch('builtins.print')
@patch('cell_cli.api_request', return_value={'synced': True, 'server': 'pool.ntp.org'})
def test_show_ntp_status_success(self, mock_req, mock_print):
show_ntp_status()
self.assertTrue(any('synced' in str(c) for c in mock_print.call_args_list))
@patch('builtins.print')
@patch('cell_cli.api_request', return_value=None)
def test_show_ntp_status_failure(self, mock_req, mock_print):
show_ntp_status()
mock_print.assert_any_call('Failed to fetch NTP status.')
class TestMainFunction(unittest.TestCase):
"""Cover main() by patching individual functions and simulating command dispatch."""
def _run_main(self, args):
import sys as _sys
from cell_cli import main
old_argv = _sys.argv
_sys.argv = ['cell_cli'] + args
try:
with patch('builtins.print'):
try:
main()
except SystemExit:
pass
finally:
_sys.argv = old_argv
def test_main_status_command(self):
with patch('cell_cli.show_status') as mock_fn:
self._run_main(['status'])
mock_fn.assert_called_once()
def test_main_peers_list_command(self):
with patch('cell_cli.list_peers') as mock_fn:
self._run_main(['peers', 'list'])
mock_fn.assert_called_once()
def test_main_peers_add_command(self):
with patch('cell_cli.add_peer') as mock_fn:
self._run_main(['peers', 'add', 'alice', '10.0.0.2', 'pubkey'])
mock_fn.assert_called_once_with('alice', '10.0.0.2', 'pubkey')
def test_main_peers_remove_command(self):
with patch('cell_cli.remove_peer') as mock_fn:
self._run_main(['peers', 'remove', 'alice'])
mock_fn.assert_called_once_with('alice')
def test_main_config_show_command(self):
with patch('cell_cli.show_config') as mock_fn:
self._run_main(['config', 'show'])
mock_fn.assert_called_once()
def test_main_config_update_command(self):
with patch('cell_cli.update_config') as mock_fn:
self._run_main(['config', 'update', 'cell_name', 'mycell'])
mock_fn.assert_called_once_with('cell_name', 'mycell')
def test_main_routing_nat_list(self):
with patch('cell_cli.list_nat_rules') as mock_fn:
self._run_main(['routing', 'nat', 'list'])
mock_fn.assert_called_once()
def test_main_routing_nat_add(self):
with patch('cell_cli.add_nat_rule') as mock_fn:
self._run_main(['routing', 'nat', 'add', '10.0.0.0/24', 'eth0'])
mock_fn.assert_called_once()
def test_main_routing_nat_delete(self):
with patch('cell_cli.delete_nat_rule') as mock_fn:
self._run_main(['routing', 'nat', 'delete', '1'])
mock_fn.assert_called_once_with('1') # argparse passes as string
def test_main_routing_peers_list(self):
with patch('cell_cli.list_peer_routes') as mock_fn:
self._run_main(['routing', 'peers', 'list'])
mock_fn.assert_called_once()
def test_main_routing_peers_add(self):
with patch('cell_cli.add_peer_route') as mock_fn:
self._run_main(['routing', 'peers', 'add', 'alice', '10.0.0.2',
'192.168.1.0/24'])
mock_fn.assert_called_once()
def test_main_routing_peers_delete(self):
with patch('cell_cli.delete_peer_route') as mock_fn:
self._run_main(['routing', 'peers', 'delete', 'alice'])
mock_fn.assert_called_once_with('alice')
def test_main_routing_firewall_list(self):
with patch('cell_cli.list_firewall_rules') as mock_fn:
self._run_main(['routing', 'firewall', 'list'])
mock_fn.assert_called_once()
def test_main_routing_firewall_add(self):
with patch('cell_cli.add_firewall_rule') as mock_fn:
self._run_main(['routing', 'firewall', 'add',
'ACCEPT', '10.0.0.0/24', 'any', 'ACCEPT'])
mock_fn.assert_called_once()
def test_main_routing_firewall_delete(self):
with patch('cell_cli.delete_firewall_rule') as mock_fn:
self._run_main(['routing', 'firewall', 'delete', '1'])
mock_fn.assert_called_once_with('1')
def test_main_services_status_command(self):
with patch('cell_cli.show_services_status') as mock_fn:
self._run_main(['services-status'])
mock_fn.assert_called_once()
def test_main_wireguard_list_command(self):
with patch('cell_cli.list_wireguard_peers') as mock_fn:
self._run_main(['wireguard-peers'])
mock_fn.assert_called_once()
def test_main_network_info_command(self):
with patch('cell_cli.show_network_info') as mock_fn:
self._run_main(['network-info'])
mock_fn.assert_called_once()
def test_main_dns_status_command(self):
with patch('cell_cli.show_dns_status') as mock_fn:
self._run_main(['dns-status'])
mock_fn.assert_called_once()
def test_main_ntp_status_command(self):
with patch('cell_cli.show_ntp_status') as mock_fn:
self._run_main(['ntp-status'])
mock_fn.assert_called_once()
if __name__ == '__main__':
unittest.main()