This commit is contained in:
Constantin
2025-09-12 23:04:52 +03:00
commit 2277b11563
127 changed files with 23640 additions and 0 deletions
BIN
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
# Test package for Personal Internet Cell API
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""
Test runner for Personal Internet Cell API
Runs all unit tests and generates coverage reports
"""
import unittest
import sys
import os
import subprocess
from pathlib import Path
def run_unit_tests():
"""Run all unit tests"""
print("🧪 Running Personal Internet Cell Unit Tests")
print("=" * 50)
# Add the api directory to Python path
api_dir = Path(__file__).parent.parent
sys.path.insert(0, str(api_dir))
# Discover and run tests
loader = unittest.TestLoader()
start_dir = Path(__file__).parent
suite = loader.discover(start_dir, pattern='test_*.py')
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
return result.wasSuccessful()
def run_pytest_with_coverage():
"""Run pytest with coverage reporting"""
print("\n📊 Running pytest with coverage...")
api_dir = Path(__file__).parent.parent
os.chdir(api_dir)
cmd = [
'python', '-m', 'pytest',
'tests/',
'--cov=app',
'--cov=scripts',
'--cov-report=html:htmlcov',
'--cov-report=term-missing',
'--cov-report=xml',
'-v'
]
try:
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
print(result.stdout)
return True
except subprocess.CalledProcessError as e:
print(f"❌ pytest failed: {e}")
print(e.stdout)
print(e.stderr)
return False
def main():
"""Main test runner"""
print("🚀 Personal Internet Cell - Test Suite")
print("=" * 50)
# Run unit tests
unit_success = run_unit_tests()
# Run pytest with coverage
pytest_success = run_pytest_with_coverage()
# Summary
print("\n" + "=" * 50)
print("📋 Test Summary")
print("=" * 50)
if unit_success and pytest_success:
print("✅ All tests passed!")
print("📊 Coverage report generated in htmlcov/")
return 0
else:
print("❌ Some tests failed!")
return 1
if __name__ == '__main__':
sys.exit(main())
+814
View File
@@ -0,0 +1,814 @@
#!/usr/bin/env python3
"""
Unit tests for Flask API endpoints
"""
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 unittest
import tempfile
import os
import json
import shutil
from unittest.mock import patch, MagicMock
from datetime import datetime
# Add parent directory to path for imports
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import app, CellManager
class TestAPIEndpoints(unittest.TestCase):
"""Test cases for API endpoints"""
def setUp(self):
"""Set up test environment"""
self.test_dir = tempfile.mkdtemp()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
os.makedirs(self.data_dir, exist_ok=True)
os.makedirs(self.config_dir, exist_ok=True)
# Mock environment variables
self.env_patcher = patch.dict(os.environ, {
'CELL_NAME': 'testcell',
'DATA_DIR': self.data_dir,
'CONFIG_DIR': self.config_dir
})
self.env_patcher.start()
# Create test client
app.config['TESTING'] = True
self.client = app.test_client()
def tearDown(self):
"""Clean up test environment"""
self.env_patcher.stop()
shutil.rmtree(self.test_dir)
def test_health_endpoint(self):
"""Test health check endpoint"""
response = self.client.get('/health')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertEqual(data['status'], 'healthy')
self.assertIn('timestamp', data)
def test_status_endpoint(self):
"""Test status endpoint"""
response = self.client.get('/api/status')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIn('cell_name', data)
self.assertIn('domain', data)
self.assertIn('peers_count', data)
self.assertIn('services', data)
self.assertIn('uptime', data)
def test_get_config_endpoint(self):
"""Test get config endpoint"""
response = self.client.get('/api/config')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIn('cell_name', data)
self.assertIn('domain', data)
self.assertIn('ip_range', data)
self.assertIn('wireguard_port', data)
def test_update_config_endpoint(self):
"""Test update config endpoint"""
update_data = {'cell_name': 'newcell'}
response = self.client.put('/api/config',
data=json.dumps(update_data),
content_type='application/json')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIn('message', data)
self.assertIn('updated', data['message'])
def test_update_config_no_data(self):
"""Test update config with no data"""
response = self.client.put('/api/config')
self.assertEqual(response.status_code, 400)
data = json.loads(response.data)
self.assertIn('error', data)
@patch('api.app.network_manager')
def test_dns_records_endpoints(self, mock_network):
# Mock get_dns_records
mock_network.get_dns_records.return_value = [{'name': 'test', 'type': 'A', 'value': '1.2.3.4'}]
response = self.client.get('/api/dns/records')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIsInstance(data, list)
# Mock add_dns_record
mock_network.add_dns_record.return_value = True
response = self.client.post('/api/dns/records', data=json.dumps({'name': 'test', 'type': 'A', 'value': '1.2.3.4'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_network.add_dns_record.side_effect = Exception('fail')
response = self.client.post('/api/dns/records', data=json.dumps({'name': 'test'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
# Mock remove_dns_record
mock_network.remove_dns_record.return_value = True
response = self.client.delete('/api/dns/records', data=json.dumps({'name': 'test', 'type': 'A'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_network.remove_dns_record.side_effect = Exception('fail')
response = self.client.delete('/api/dns/records', data=json.dumps({'name': 'test'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
@patch('api.app.network_manager')
def test_dhcp_endpoints(self, mock_network):
# Mock get_dhcp_leases
mock_network.get_dhcp_leases.return_value = [{'ip': '10.0.0.2', 'mac': '00:11:22:33:44:55'}]
response = self.client.get('/api/dhcp/leases')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIsInstance(data, list)
# Mock add_dhcp_reservation
mock_network.add_dhcp_reservation.return_value = True
response = self.client.post('/api/dhcp/reservations', data=json.dumps({'ip': '10.0.0.2', 'mac': '00:11:22:33:44:55'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_network.add_dhcp_reservation.side_effect = Exception('fail')
response = self.client.post('/api/dhcp/reservations', data=json.dumps({'ip': '10.0.0.2'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
# Mock remove_dhcp_reservation
mock_network.remove_dhcp_reservation.return_value = True
response = self.client.delete('/api/dhcp/reservations', data=json.dumps({'ip': '10.0.0.2'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_network.remove_dhcp_reservation.side_effect = Exception('fail')
response = self.client.delete('/api/dhcp/reservations', data=json.dumps({'ip': '10.0.0.2'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
@patch('api.app.network_manager')
def test_ntp_status_endpoint(self, mock_network):
# Mock get_ntp_status
mock_network.get_ntp_status.return_value = {'running': True, 'stats': {}}
response = self.client.get('/api/ntp/status')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIn('running', data)
# Simulate error
mock_network.get_ntp_status.side_effect = Exception('fail')
response = self.client.get('/api/ntp/status')
self.assertEqual(response.status_code, 500)
@patch('api.app.network_manager')
def test_network_test_endpoint(self, mock_network):
# Mock test_connectivity
mock_network.test_connectivity.return_value = {'success': True, 'output': 'ok'}
response = self.client.post('/api/network/test', data=json.dumps({'target': '8.8.8.8'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIn('success', data)
# Simulate error
mock_network.test_connectivity.side_effect = Exception('fail')
response = self.client.post('/api/network/test', data=json.dumps({'target': '8.8.8.8'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
@patch('api.app.wireguard_manager')
def test_wireguard_endpoints(self, mock_wg):
# /api/wireguard/keys (GET)
mock_wg.get_keys.return_value = {'public_key': 'pub', 'private_key': 'priv'}
response = self.client.get('/api/wireguard/keys')
self.assertEqual(response.status_code, 200)
self.assertIn('public_key', json.loads(response.data))
# Simulate error
mock_wg.get_keys.side_effect = Exception('fail')
response = self.client.get('/api/wireguard/keys')
self.assertEqual(response.status_code, 500)
mock_wg.get_keys.side_effect = None
# /api/wireguard/keys/peer (POST)
mock_wg.generate_peer_keys.return_value = {'peer_key': 'peer'}
response = self.client.post('/api/wireguard/keys/peer', data=json.dumps({'name': 'peer'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_wg.generate_peer_keys.side_effect = Exception('fail')
response = self.client.post('/api/wireguard/keys/peer', data=json.dumps({'name': 'peer'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_wg.generate_peer_keys.side_effect = None
# /api/wireguard/config (GET)
mock_wg.get_config.return_value = {'config': 'wg0'}
response = self.client.get('/api/wireguard/config')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_wg.get_config.side_effect = Exception('fail')
response = self.client.get('/api/wireguard/config')
self.assertEqual(response.status_code, 500)
mock_wg.get_config.side_effect = None
# /api/wireguard/peers (GET)
mock_wg.get_peers.return_value = [{'peer': 'peer1'}]
response = self.client.get('/api/wireguard/peers')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_wg.get_peers.side_effect = Exception('fail')
response = self.client.get('/api/wireguard/peers')
self.assertEqual(response.status_code, 500)
mock_wg.get_peers.side_effect = None
# /api/wireguard/peers (POST)
mock_wg.add_peer.return_value = {'result': 'ok'}
response = self.client.post('/api/wireguard/peers', data=json.dumps({'peer': 'peer1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_wg.add_peer.side_effect = Exception('fail')
response = self.client.post('/api/wireguard/peers', data=json.dumps({'peer': 'peer1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_wg.add_peer.side_effect = None
# /api/wireguard/peers (DELETE)
mock_wg.remove_peer.return_value = {'result': 'ok'}
response = self.client.delete('/api/wireguard/peers', data=json.dumps({'peer': 'peer1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_wg.remove_peer.side_effect = Exception('fail')
response = self.client.delete('/api/wireguard/peers', data=json.dumps({'peer': 'peer1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_wg.remove_peer.side_effect = None
# /api/wireguard/status (GET)
mock_wg.get_status.return_value = {'status': 'ok'}
response = self.client.get('/api/wireguard/status')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_wg.get_status.side_effect = Exception('fail')
response = self.client.get('/api/wireguard/status')
self.assertEqual(response.status_code, 500)
mock_wg.get_status.side_effect = None
# /api/wireguard/connectivity (POST)
mock_wg.test_connectivity.return_value = {'success': True}
response = self.client.post('/api/wireguard/connectivity', data=json.dumps({'target': 'peer1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_wg.test_connectivity.side_effect = Exception('fail')
response = self.client.post('/api/wireguard/connectivity', data=json.dumps({'target': 'peer1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_wg.test_connectivity.side_effect = None
# /api/wireguard/peers/ip (PUT)
mock_wg.update_peer_ip.return_value = {'success': True}
response = self.client.put('/api/wireguard/peers/ip', data=json.dumps({'peer': 'peer1', 'ip': '10.0.0.2'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_wg.update_peer_ip.side_effect = Exception('fail')
response = self.client.put('/api/wireguard/peers/ip', data=json.dumps({'peer': 'peer1', 'ip': '10.0.0.2'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_wg.update_peer_ip.side_effect = None
# /api/wireguard/peers/config (POST)
mock_wg.get_peer_config.return_value = {'config': 'peer1'}
response = self.client.post('/api/wireguard/peers/config', data=json.dumps({'peer': 'peer1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Simulate error
mock_wg.get_peer_config.side_effect = Exception('fail')
response = self.client.post('/api/wireguard/peers/config', data=json.dumps({'peer': 'peer1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_wg.get_peer_config.side_effect = None
@patch('api.app.peer_registry')
def test_peer_registry_endpoints(self, mock_peers):
# /api/peers (GET)
mock_peers.list_peers.return_value = [{'peer': 'peer1', 'ip': '10.0.0.2'}]
response = self.client.get('/api/peers')
self.assertEqual(response.status_code, 200)
self.assertIsInstance(json.loads(response.data), list)
# Simulate error
mock_peers.list_peers.side_effect = Exception('fail')
response = self.client.get('/api/peers')
self.assertEqual(response.status_code, 500)
mock_peers.list_peers.side_effect = None
# /api/peers (POST)
mock_peers.add_peer.return_value = True
response = self.client.post('/api/peers', data=json.dumps({'name': 'peer1', 'ip': '10.0.0.2', 'public_key': 'key'}), content_type='application/json')
self.assertEqual(response.status_code, 201)
# Duplicate
mock_peers.add_peer.return_value = False
response = self.client.post('/api/peers', data=json.dumps({'name': 'peer1', 'ip': '10.0.0.2', 'public_key': 'key'}), content_type='application/json')
self.assertEqual(response.status_code, 400)
# Missing field
response = self.client.post('/api/peers', data=json.dumps({'ip': '10.0.0.2', 'public_key': 'key'}), content_type='application/json')
self.assertEqual(response.status_code, 400)
# Simulate error
mock_peers.add_peer.side_effect = Exception('fail')
response = self.client.post('/api/peers', data=json.dumps({'name': 'peer2', 'ip': '10.0.0.3', 'public_key': 'key'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_peers.add_peer.side_effect = None
# /api/peers/<peer_name> (DELETE)
mock_peers.remove_peer.return_value = True
response = self.client.delete('/api/peers/peer1')
self.assertEqual(response.status_code, 200)
mock_peers.remove_peer.return_value = False
response = self.client.delete('/api/peers/peer1')
self.assertEqual(response.status_code, 200)
mock_peers.remove_peer.side_effect = Exception('fail')
response = self.client.delete('/api/peers/peer1')
self.assertEqual(response.status_code, 500)
mock_peers.remove_peer.side_effect = None
# /api/peers/register (POST)
mock_peers.register_peer.return_value = {'result': 'ok'}
response = self.client.post('/api/peers/register', data=json.dumps({'peer': 'peer1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_peers.register_peer.side_effect = Exception('fail')
response = self.client.post('/api/peers/register', data=json.dumps({'peer': 'peer1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_peers.register_peer.side_effect = None
# /api/peers/<peer_name>/unregister (DELETE)
mock_peers.unregister_peer.return_value = {'result': 'ok'}
response = self.client.delete('/api/peers/peer1/unregister')
self.assertEqual(response.status_code, 200)
mock_peers.unregister_peer.side_effect = Exception('fail')
response = self.client.delete('/api/peers/peer1/unregister')
self.assertEqual(response.status_code, 500)
mock_peers.unregister_peer.side_effect = None
# /api/peers/<peer_name>/update-ip (PUT)
mock_peers.update_peer_ip.return_value = True
response = self.client.put('/api/peers/peer1/update-ip', data=json.dumps({'ip': '10.0.0.3'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_peers.update_peer_ip.return_value = False
response = self.client.put('/api/peers/peer1/update-ip', data=json.dumps({'ip': '10.0.0.3'}), content_type='application/json')
self.assertEqual(response.status_code, 404)
mock_peers.update_peer_ip.side_effect = Exception('fail')
response = self.client.put('/api/peers/peer1/update-ip', data=json.dumps({'ip': '10.0.0.3'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_peers.update_peer_ip.side_effect = None
@patch('api.app.email_manager')
def test_email_endpoints(self, mock_email):
# Ensure all relevant mock methods return JSON-serializable values
mock_email.get_users.return_value = [{'username': 'user1', 'domain': 'cell', 'email': 'user1@cell'}]
mock_email.create_user.return_value = True
mock_email.delete_user.return_value = True
mock_email.get_status.return_value = {'postfix_running': True, 'dovecot_running': True, 'total_users': 1, 'total_size_bytes': 0, 'total_size_mb': 0.0, 'users': [{'username': 'user1', 'domain': 'cell', 'email': 'user1@cell'}]}
mock_email.test_connectivity.return_value = {'smtp': {'success': True, 'message': 'SMTP server responding'}}
mock_email.send_email.return_value = True
mock_email.get_mailbox_info.return_value = {'username': 'user1', 'domain': 'cell', 'email': 'user1@cell', 'total_messages': 0, 'total_size_bytes': 0, 'total_size_mb': 0.0, 'folders': {}}
# /api/email/users (GET)
response = self.client.get('/api/email/users')
self.assertEqual(response.status_code, 200)
self.assertIsInstance(json.loads(response.data), list)
mock_email.get_users.side_effect = Exception('fail')
response = self.client.get('/api/email/users')
self.assertEqual(response.status_code, 500)
mock_email.get_users.side_effect = None
# /api/email/users (POST)
response = self.client.post('/api/email/users', data=json.dumps({'username': 'user1', 'domain': 'cell', 'password': 'pw'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_email.create_user.side_effect = Exception('fail')
response = self.client.post('/api/email/users', data=json.dumps({'username': 'user1', 'domain': 'cell', 'password': 'pw'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_email.create_user.side_effect = None
# /api/email/users/<username> (DELETE)
response = self.client.delete('/api/email/users/user1')
self.assertEqual(response.status_code, 200)
mock_email.delete_user.side_effect = Exception('fail')
response = self.client.delete('/api/email/users/user1')
self.assertEqual(response.status_code, 500)
mock_email.delete_user.side_effect = None
# /api/email/status (GET)
response = self.client.get('/api/email/status')
self.assertEqual(response.status_code, 200)
mock_email.get_status.side_effect = Exception('fail')
response = self.client.get('/api/email/status')
self.assertEqual(response.status_code, 500)
mock_email.get_status.side_effect = None
# /api/email/connectivity (GET)
response = self.client.get('/api/email/connectivity')
self.assertEqual(response.status_code, 200)
mock_email.test_connectivity.side_effect = Exception('fail')
response = self.client.get('/api/email/connectivity')
self.assertEqual(response.status_code, 500)
mock_email.test_connectivity.side_effect = None
# /api/email/send (POST)
response = self.client.post('/api/email/send', data=json.dumps({'from': 'a', 'to': 'b', 'subject': 's', 'body': 'b'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_email.send_email.side_effect = Exception('fail')
response = self.client.post('/api/email/send', data=json.dumps({'from': 'a', 'to': 'b', 'subject': 's', 'body': 'b'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_email.send_email.side_effect = None
# /api/email/mailbox/<username> (GET)
response = self.client.get('/api/email/mailbox/user1')
self.assertEqual(response.status_code, 200)
mock_email.get_mailbox_info.side_effect = Exception('fail')
response = self.client.get('/api/email/mailbox/user1')
self.assertEqual(response.status_code, 500)
mock_email.get_mailbox_info.side_effect = None
@patch('api.app.calendar_manager')
def test_calendar_endpoints(self, mock_calendar):
# Mock return values for all relevant calendar_manager methods
mock_calendar.get_users.return_value = [{'username': 'user1', 'collections': {'calendars': ['cal1'], 'contacts': ['c1']}}]
mock_calendar.create_user.return_value = True
mock_calendar.delete_user.return_value = True
mock_calendar.create_calendar.return_value = {'calendar': 'cal1'}
mock_calendar.add_event.return_value = {'event': 'event1'}
mock_calendar.get_events.return_value = [{'event': 'event1'}]
mock_calendar.get_status.return_value = {'radicale_running': True, 'total_users': 1, 'total_calendars': 1, 'total_contacts': 1, 'users': [{'username': 'user1', 'collections': {'calendars': ['cal1'], 'contacts': ['c1']}}]}
mock_calendar.test_connectivity.return_value = {'success': True}
# /api/calendar/users (GET)
response = self.client.get('/api/calendar/users')
self.assertEqual(response.status_code, 200)
self.assertIsInstance(json.loads(response.data), list)
mock_calendar.get_users.side_effect = Exception('fail')
response = self.client.get('/api/calendar/users')
self.assertEqual(response.status_code, 500)
mock_calendar.get_users.side_effect = None
# /api/calendar/users (POST)
response = self.client.post('/api/calendar/users', data=json.dumps({'username': 'user1', 'password': 'pw'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_calendar.create_user.side_effect = Exception('fail')
response = self.client.post('/api/calendar/users', data=json.dumps({'username': 'user1', 'password': 'pw'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_calendar.create_user.side_effect = None
# /api/calendar/users/<username> (DELETE)
response = self.client.delete('/api/calendar/users/user1')
self.assertEqual(response.status_code, 200)
mock_calendar.delete_user.side_effect = Exception('fail')
response = self.client.delete('/api/calendar/users/user1')
self.assertEqual(response.status_code, 500)
mock_calendar.delete_user.side_effect = None
# /api/calendar/calendars (POST)
response = self.client.post('/api/calendar/calendars', data=json.dumps({'username': 'user1', 'calendar_name': 'cal1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_calendar.create_calendar.side_effect = Exception('fail')
response = self.client.post('/api/calendar/calendars', data=json.dumps({'username': 'user1', 'calendar_name': 'cal1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_calendar.create_calendar.side_effect = None
# /api/calendar/events (POST)
response = self.client.post('/api/calendar/events', data=json.dumps({'username': 'user1', 'calendar_name': 'cal1', 'event': 'event1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_calendar.add_event.side_effect = Exception('fail')
response = self.client.post('/api/calendar/events', data=json.dumps({'username': 'user1', 'calendar_name': 'cal1', 'event': 'event1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_calendar.add_event.side_effect = None
# /api/calendar/events/<username>/<calendar_name> (GET)
response = self.client.get('/api/calendar/events/user1/cal1')
self.assertEqual(response.status_code, 200)
mock_calendar.get_events.side_effect = Exception('fail')
response = self.client.get('/api/calendar/events/user1/cal1')
self.assertEqual(response.status_code, 500)
mock_calendar.get_events.side_effect = None
# /api/calendar/status (GET)
response = self.client.get('/api/calendar/status')
self.assertEqual(response.status_code, 200)
mock_calendar.get_status.side_effect = Exception('fail')
response = self.client.get('/api/calendar/status')
self.assertEqual(response.status_code, 500)
mock_calendar.get_status.side_effect = None
# /api/calendar/connectivity (GET)
response = self.client.get('/api/calendar/connectivity')
self.assertEqual(response.status_code, 200)
mock_calendar.test_connectivity.side_effect = Exception('fail')
response = self.client.get('/api/calendar/connectivity')
self.assertEqual(response.status_code, 500)
mock_calendar.test_connectivity.side_effect = None
@patch('api.app.file_manager')
def test_file_endpoints(self, mock_file):
# Mock return values for all relevant file_manager methods
mock_file.get_users.return_value = [{'username': 'user1', 'storage_info': {'total_files': 1, 'total_size_bytes': 1000}}]
mock_file.create_user.return_value = True
mock_file.delete_user.return_value = True
mock_file.get_status.return_value = {'webdav_running': True, 'total_users': 1, 'total_files': 1, 'total_size_bytes': 1000, 'total_size_mb': 1.0, 'users': [{'username': 'user1', 'storage_info': {'total_files': 1, 'total_size_bytes': 1000}}]}
mock_file.test_connectivity.return_value = {'success': True}
# /api/files/users (GET)
response = self.client.get('/api/files/users')
self.assertEqual(response.status_code, 200)
self.assertIsInstance(json.loads(response.data), list)
mock_file.get_users.side_effect = Exception('fail')
response = self.client.get('/api/files/users')
self.assertEqual(response.status_code, 500)
mock_file.get_users.side_effect = None
# /api/files/users (POST)
response = self.client.post('/api/files/users', data=json.dumps({'username': 'user1', 'password': 'pw'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_file.create_user.side_effect = Exception('fail')
response = self.client.post('/api/files/users', data=json.dumps({'username': 'user1', 'password': 'pw'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_file.create_user.side_effect = None
# /api/files/users/<username> (DELETE)
response = self.client.delete('/api/files/users/user1')
self.assertEqual(response.status_code, 200)
mock_file.delete_user.side_effect = Exception('fail')
response = self.client.delete('/api/files/users/user1')
self.assertEqual(response.status_code, 500)
mock_file.delete_user.side_effect = None
# /api/files/status (GET)
response = self.client.get('/api/files/status')
self.assertEqual(response.status_code, 200)
mock_file.get_status.side_effect = Exception('fail')
response = self.client.get('/api/files/status')
self.assertEqual(response.status_code, 500)
mock_file.get_status.side_effect = None
# /api/files/connectivity (GET)
response = self.client.get('/api/files/connectivity')
self.assertEqual(response.status_code, 200)
mock_file.test_connectivity.side_effect = Exception('fail')
response = self.client.get('/api/files/connectivity')
self.assertEqual(response.status_code, 500)
mock_file.test_connectivity.side_effect = None
@patch('api.app.routing_manager')
def test_routing_endpoints(self, mock_routing):
# Mock return values for all relevant routing_manager methods
mock_routing.get_status.return_value = {'routing_running': True, 'routes': []}
mock_routing.add_nat_rule.return_value = {'result': 'ok'}
mock_routing.get_nat_rules.return_value = [{'id': 1, 'rule': 'nat'}]
mock_routing.remove_nat_rule.return_value = {'result': 'ok'}
mock_routing.add_firewall_rule.return_value = {'result': 'ok'}
mock_routing.get_firewall_rules.return_value = [{'id': 1, 'rule': 'fw'}]
mock_routing.add_peer_route.return_value = {'result': 'ok'}
mock_routing.get_peer_routes.return_value = [{'peer': 'peer1', 'route': '10.0.0.2'}]
mock_routing.remove_peer_route.return_value = {'result': 'ok'}
mock_routing.add_exit_node.return_value = {'result': 'ok'}
mock_routing.add_bridge_route.return_value = {'result': 'ok'}
mock_routing.add_split_route.return_value = {'result': 'ok'}
mock_routing.test_connectivity.return_value = {'success': True}
mock_routing.get_routing_logs.return_value = {'logs': 'logdata'}
# /api/routing/status (GET)
response = self.client.get('/api/routing/status')
self.assertEqual(response.status_code, 200)
mock_routing.get_status.side_effect = Exception('fail')
response = self.client.get('/api/routing/status')
self.assertEqual(response.status_code, 500)
mock_routing.get_status.side_effect = None
# /api/routing/nat (POST)
response = self.client.post('/api/routing/nat', data=json.dumps({'rule': 'nat'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_routing.add_nat_rule.side_effect = Exception('fail')
response = self.client.post('/api/routing/nat', data=json.dumps({'rule': 'nat'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_routing.add_nat_rule.side_effect = None
# /api/routing/nat (GET)
response = self.client.get('/api/routing/nat')
self.assertEqual(response.status_code, 200)
mock_routing.get_nat_rules.side_effect = Exception('fail')
response = self.client.get('/api/routing/nat')
self.assertEqual(response.status_code, 500)
mock_routing.get_nat_rules.side_effect = None
# /api/routing/nat/<rule_id> (DELETE)
response = self.client.delete('/api/routing/nat/1')
self.assertEqual(response.status_code, 200)
mock_routing.remove_nat_rule.side_effect = Exception('fail')
response = self.client.delete('/api/routing/nat/1')
self.assertEqual(response.status_code, 500)
mock_routing.remove_nat_rule.side_effect = None
# /api/routing/firewall (POST)
response = self.client.post('/api/routing/firewall', data=json.dumps({'rule': 'fw'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_routing.add_firewall_rule.side_effect = Exception('fail')
response = self.client.post('/api/routing/firewall', data=json.dumps({'rule': 'fw'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_routing.add_firewall_rule.side_effect = None
# /api/routing/firewall (GET)
response = self.client.get('/api/routing/firewall')
self.assertEqual(response.status_code, 200)
mock_routing.get_firewall_rules.side_effect = Exception('fail')
response = self.client.get('/api/routing/firewall')
self.assertEqual(response.status_code, 500)
mock_routing.get_firewall_rules.side_effect = None
# /api/routing/peers (POST)
response = self.client.post('/api/routing/peers', data=json.dumps({'peer': 'peer1', 'route': '10.0.0.2'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_routing.add_peer_route.side_effect = Exception('fail')
response = self.client.post('/api/routing/peers', data=json.dumps({'peer': 'peer1', 'route': '10.0.0.2'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_routing.add_peer_route.side_effect = None
# /api/routing/peers (GET)
response = self.client.get('/api/routing/peers')
self.assertEqual(response.status_code, 200)
mock_routing.get_peer_routes.side_effect = Exception('fail')
response = self.client.get('/api/routing/peers')
self.assertEqual(response.status_code, 500)
mock_routing.get_peer_routes.side_effect = None
# /api/routing/peers/<peer_name> (DELETE)
response = self.client.delete('/api/routing/peers/peer1')
self.assertEqual(response.status_code, 200)
mock_routing.remove_peer_route.side_effect = Exception('fail')
response = self.client.delete('/api/routing/peers/peer1')
self.assertEqual(response.status_code, 500)
mock_routing.remove_peer_route.side_effect = None
# /api/routing/exit-nodes (POST)
response = self.client.post('/api/routing/exit-nodes', data=json.dumps({'node': 'exit1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_routing.add_exit_node.side_effect = Exception('fail')
response = self.client.post('/api/routing/exit-nodes', data=json.dumps({'node': 'exit1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_routing.add_exit_node.side_effect = None
# /api/routing/bridge (POST)
response = self.client.post('/api/routing/bridge', data=json.dumps({'bridge': 'br1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_routing.add_bridge_route.side_effect = Exception('fail')
response = self.client.post('/api/routing/bridge', data=json.dumps({'bridge': 'br1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_routing.add_bridge_route.side_effect = None
# /api/routing/split (POST)
response = self.client.post('/api/routing/split', data=json.dumps({'split': 'sp1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_routing.add_split_route.side_effect = Exception('fail')
response = self.client.post('/api/routing/split', data=json.dumps({'split': 'sp1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_routing.add_split_route.side_effect = None
# /api/routing/connectivity (POST)
response = self.client.post('/api/routing/connectivity', data=json.dumps({'target': '10.0.0.2'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_routing.test_connectivity.side_effect = Exception('fail')
response = self.client.post('/api/routing/connectivity', data=json.dumps({'target': '10.0.0.2'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_routing.test_connectivity.side_effect = None
# /api/routing/logs (GET)
mock_routing.get_logs.return_value = {
'iptables': 'iptables log data',
'routing': 'routing log data',
'routes': 'route log data'
}
response = self.client.get('/api/routing/logs')
self.assertEqual(response.status_code, 200)
mock_routing.get_logs.side_effect = Exception('fail')
response = self.client.get('/api/routing/logs')
self.assertEqual(response.status_code, 500)
mock_routing.get_logs.side_effect = None
@patch('api.app.app.vault_manager')
def test_vault_endpoints(self, mock_vault):
# Mock return values for all relevant vault_manager methods
mock_vault.get_status = MagicMock(return_value={'vault_running': True, 'certs': 2})
mock_vault.list_certificates = MagicMock(return_value=[{'common_name': 'test', 'valid': True}])
mock_vault.generate_certificate = MagicMock(return_value={'certificate': 'certdata'})
mock_vault.revoke_certificate = MagicMock(return_value=True)
mock_vault.get_ca_certificate = MagicMock(return_value='ca_cert_data')
mock_vault.get_age_public_key = MagicMock(return_value='age_pubkey')
mock_vault.get_trusted_keys = MagicMock(return_value=[{'name': 'key1', 'public_key': 'pk1'}])
mock_vault.add_trusted_key = MagicMock(return_value=True)
mock_vault.remove_trusted_key = MagicMock(return_value=True)
mock_vault.verify_trust_chain = MagicMock(return_value=True)
mock_vault.get_trust_chains = MagicMock(return_value=[{'chain': 'chain1'}])
# /api/vault/status (GET)
response = self.client.get('/api/vault/status')
self.assertEqual(response.status_code, 200)
mock_vault.get_status.side_effect = Exception('fail')
response = self.client.get('/api/vault/status')
self.assertEqual(response.status_code, 500)
mock_vault.get_status.side_effect = None
# /api/vault/certificates (GET)
response = self.client.get('/api/vault/certificates')
self.assertEqual(response.status_code, 200)
mock_vault.list_certificates.side_effect = Exception('fail')
response = self.client.get('/api/vault/certificates')
self.assertEqual(response.status_code, 500)
mock_vault.list_certificates.side_effect = None
# /api/vault/certificates (POST)
response = self.client.post('/api/vault/certificates', data=json.dumps({'common_name': 'test'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_vault.generate_certificate.side_effect = Exception('fail')
response = self.client.post('/api/vault/certificates', data=json.dumps({'common_name': 'test'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_vault.generate_certificate.side_effect = None
# /api/vault/certificates/<common_name> (DELETE)
response = self.client.delete('/api/vault/certificates/test')
self.assertEqual(response.status_code, 200)
mock_vault.revoke_certificate.side_effect = Exception('fail')
response = self.client.delete('/api/vault/certificates/test')
self.assertEqual(response.status_code, 500)
mock_vault.revoke_certificate.side_effect = None
# /api/vault/ca/certificate (GET)
response = self.client.get('/api/vault/ca/certificate')
self.assertEqual(response.status_code, 200)
mock_vault.get_ca_certificate.side_effect = Exception('fail')
response = self.client.get('/api/vault/ca/certificate')
self.assertEqual(response.status_code, 500)
mock_vault.get_ca_certificate.side_effect = None
# /api/vault/age/public-key (GET)
response = self.client.get('/api/vault/age/public-key')
self.assertEqual(response.status_code, 200)
mock_vault.get_age_public_key.side_effect = Exception('fail')
response = self.client.get('/api/vault/age/public-key')
self.assertEqual(response.status_code, 500)
mock_vault.get_age_public_key.side_effect = None
# /api/vault/trust/keys (GET)
response = self.client.get('/api/vault/trust/keys')
self.assertEqual(response.status_code, 200)
mock_vault.get_trusted_keys.side_effect = Exception('fail')
response = self.client.get('/api/vault/trust/keys')
self.assertEqual(response.status_code, 500)
mock_vault.get_trusted_keys.side_effect = None
# /api/vault/trust/keys (POST)
response = self.client.post('/api/vault/trust/keys', data=json.dumps({'name': 'key1', 'public_key': 'pk1'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_vault.add_trusted_key.side_effect = Exception('fail')
response = self.client.post('/api/vault/trust/keys', data=json.dumps({'name': 'key1', 'public_key': 'pk1'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_vault.add_trusted_key.side_effect = None
# /api/vault/trust/keys/<name> (DELETE)
response = self.client.delete('/api/vault/trust/keys/key1')
self.assertEqual(response.status_code, 200)
mock_vault.remove_trusted_key.side_effect = Exception('fail')
response = self.client.delete('/api/vault/trust/keys/key1')
self.assertEqual(response.status_code, 500)
mock_vault.remove_trusted_key.side_effect = None
# /api/vault/trust/verify (POST)
response = self.client.post('/api/vault/trust/verify', data=json.dumps({'peer_name': 'peer1', 'signature': 'sig', 'data': 'data'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
mock_vault.verify_trust_chain.side_effect = Exception('fail')
response = self.client.post('/api/vault/trust/verify', data=json.dumps({'peer_name': 'peer1', 'signature': 'sig', 'data': 'data'}), content_type='application/json')
self.assertEqual(response.status_code, 500)
mock_vault.verify_trust_chain.side_effect = None
# /api/vault/trust/chains (GET)
response = self.client.get('/api/vault/trust/chains')
self.assertEqual(response.status_code, 200)
mock_vault.get_trust_chains.side_effect = Exception('fail')
response = self.client.get('/api/vault/trust/chains')
self.assertEqual(response.status_code, 500)
mock_vault.get_trust_chains.side_effect = None
@patch('api.app.app.vault_manager')
def test_secrets_api_endpoints(self, mock_vault):
mock_vault.list_secrets.return_value = ['API_KEY']
mock_vault.store_secret.return_value = True
mock_vault.get_secret.return_value = 'supersecret'
mock_vault.delete_secret.return_value = True
# List secrets
response = self.client.get('/api/vault/secrets')
self.assertEqual(response.status_code, 200)
self.assertIn('API_KEY', json.loads(response.data)['secrets'])
# Store secret
response = self.client.post('/api/vault/secrets', data=json.dumps({'name': 'API_KEY', 'value': 'supersecret'}), content_type='application/json')
self.assertEqual(response.status_code, 200)
# Get secret
response = self.client.get('/api/vault/secrets/API_KEY')
self.assertEqual(response.status_code, 200)
self.assertEqual(json.loads(response.data)['value'], 'supersecret')
# Delete secret
response = self.client.delete('/api/vault/secrets/API_KEY')
self.assertEqual(response.status_code, 200)
# Container creation with secrets
mock_vault.get_secret.side_effect = lambda name: 'supersecret' if name == 'API_KEY' else None
with patch('api.app.container_manager') as mock_container:
mock_container.create_container.return_value = {'id': 'cid', 'name': 'cname'}
data = {'image': 'nginx', 'secrets': ['API_KEY']}
response = self.client.post('/api/containers', data=json.dumps(data), content_type='application/json')
self.assertEqual(response.status_code, 200)
args, kwargs = mock_container.create_container.call_args
self.assertIn('API_KEY', kwargs['env'])
self.assertEqual(kwargs['env']['API_KEY'], 'supersecret')
@patch('api.app.container_manager')
def test_container_endpoints(self, mock_container):
# Simulate local request
with self.client as c:
c.environ_base['REMOTE_ADDR'] = '127.0.0.1'
# List containers
mock_container.list_containers.return_value = [{'id': 'abc', 'name': 'test', 'status': 'running', 'image': ['img'], 'labels': {}}]
response = c.get('/api/containers')
self.assertEqual(response.status_code, 200)
self.assertIsInstance(json.loads(response.data), list)
mock_container.list_containers.side_effect = Exception('fail')
response = c.get('/api/containers')
self.assertEqual(response.status_code, 500)
mock_container.list_containers.side_effect = None
# Start container
mock_container.start_container.return_value = True
response = c.post('/api/containers/test/start')
self.assertEqual(response.status_code, 200)
mock_container.start_container.side_effect = Exception('fail')
response = c.post('/api/containers/test/start')
self.assertEqual(response.status_code, 500)
mock_container.start_container.side_effect = None
# Stop container
mock_container.stop_container.return_value = True
response = c.post('/api/containers/test/stop')
self.assertEqual(response.status_code, 200)
mock_container.stop_container.side_effect = Exception('fail')
response = c.post('/api/containers/test/stop')
self.assertEqual(response.status_code, 500)
mock_container.stop_container.side_effect = None
# Restart container
mock_container.restart_container.return_value = True
response = c.post('/api/containers/test/restart')
self.assertEqual(response.status_code, 200)
mock_container.restart_container.side_effect = Exception('fail')
response = c.post('/api/containers/test/restart')
self.assertEqual(response.status_code, 500)
mock_container.restart_container.side_effect = None
# Simulate non-local request
with self.client as c:
c.environ_base['REMOTE_ADDR'] = '8.8.8.8'
response = c.get('/api/containers')
self.assertEqual(response.status_code, 403)
response = c.post('/api/containers/test/start')
self.assertEqual(response.status_code, 403)
response = c.post('/api/containers/test/stop')
self.assertEqual(response.status_code, 403)
response = c.post('/api/containers/test/restart')
self.assertEqual(response.status_code, 403)
if __name__ == '__main__':
unittest.main()
+141
View File
@@ -0,0 +1,141 @@
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 unittest
from unittest.mock import patch, MagicMock
import threading
import time
import os
import sys
import types
import builtins
# Patch LOG_LEVEL and LOG_FILE in environment before importing app_module
# os.environ['LOG_LEVEL'] = 'INFO'
# os.environ['LOG_FILE'] = 'test.log'
# Patch manager classes in builtins before importing api.app
manager_names = [
'NetworkManager', 'WireGuardManager', 'PeerRegistry', 'EmailManager',
'CalendarManager', 'FileManager', 'RoutingManager', 'CellManager', 'VaultManager', 'ContainerManager'
]
for name in manager_names:
setattr(builtins, name, MagicMock)
builtins.LOG_LEVEL = 'INFO' # type: ignore[attr-defined]
builtins.LOG_FILE = 'test.log' # type: ignore[attr-defined]
sys.path.append(os.path.join(os.path.dirname(__file__), '../api'))
import app as app_module
# LOG_LEVEL = 'INFO'
# LOG_FILE = 'test.log'
class TestAppMisc(unittest.TestCase):
def setUp(self):
# Patch managers to avoid side effects
self.patches = [
patch.object(app_module, 'network_manager', MagicMock()),
patch.object(app_module, 'wireguard_manager', MagicMock()),
patch.object(app_module, 'peer_registry', MagicMock()),
patch.object(app_module, 'email_manager', MagicMock()),
patch.object(app_module, 'calendar_manager', MagicMock()),
patch.object(app_module, 'file_manager', MagicMock()),
patch.object(app_module, 'routing_manager', MagicMock()),
patch.object(app_module, 'cell_manager', MagicMock()),
patch.object(app_module, 'container_manager', MagicMock()),
]
for p in self.patches:
p.start()
# Patch vault_manager on app (setattr to avoid linter error)
self._original_vault_manager = getattr(app_module.app, 'vault_manager', None) # type: ignore[attr-defined]
setattr(app_module.app, 'vault_manager', MagicMock()) # type: ignore[attr-defined]
def tearDown(self):
for p in self.patches:
p.stop()
# Remove or restore vault_manager
if self._original_vault_manager is not None:
setattr(app_module.app, 'vault_manager', self._original_vault_manager) # type: ignore[attr-defined]
else:
delattr(app_module.app, 'vault_manager') # type: ignore[attr-defined]
def test_health_monitor_thread_runs(self):
# Patch health_history and service_alert_counters
with patch.object(app_module, 'health_history', new=[]), \
patch.object(app_module, 'service_alert_counters', new={}), \
app_module.app.app_context():
# Patch managers to return healthy
app_module.network_manager.get_status.return_value = {'ok': True}
app_module.wireguard_manager.get_status.return_value = {'ok': True}
app_module.email_manager.get_status.return_value = {'ok': True}
app_module.calendar_manager.get_status.return_value = {'ok': True}
app_module.file_manager.get_status.return_value = {'ok': True}
app_module.routing_manager.get_status.return_value = {'ok': True}
app_module.app.vault_manager.get_status.return_value = {'ok': True}
# Run one health check
result = app_module.perform_health_check()
self.assertIn('network', result)
self.assertIn('alerts', result)
def test_enrich_log_context_sets_context(self):
# Simulate Flask request context
class DummyRequest:
remote_addr = '127.0.0.1'
method = 'GET'
path = '/test'
user = type('User', (), {'id': 'user1'})()
with patch('api.app.request', new=DummyRequest()):
app_module.enrich_log_context()
ctx = app_module.request_context.get()
self.assertEqual(ctx['client_ip'], '127.0.0.1')
self.assertEqual(ctx['method'], 'GET')
self.assertEqual(ctx['path'], '/test')
self.assertEqual(ctx['user'], 'user1')
def test_is_local_request(self):
class DummyRequest:
remote_addr = '127.0.0.1'
with patch('api.app.request', new=DummyRequest()):
self.assertTrue(app_module.is_local_request())
class DummyRequest2:
remote_addr = '8.8.8.8'
with patch('api.app.request', new=DummyRequest2()):
self.assertFalse(app_module.is_local_request())
def test_health_check_exception(self):
# Patch datetime to raise exception
with patch('api.app.datetime') as mock_dt, app_module.app.app_context():
mock_dt.utcnow.side_effect = Exception('fail')
client = app_module.app.test_client()
response = client.get('/health')
self.assertIn(response.status_code, (200, 500))
data = response.get_json(silent=True)
# Accept either a valid JSON with 'error' or None
if data is not None:
self.assertIn('error', data)
def test_get_cell_status_exception(self):
with app_module.app.app_context():
app_module.network_manager.get_status.side_effect = Exception('fail')
client = app_module.app.test_client()
response = client.get('/api/status')
self.assertEqual(response.status_code, 500)
self.assertIn('error', response.get_json())
def test_get_config_exception(self):
with patch('api.app.datetime') as mock_dt, app_module.app.app_context():
mock_dt.utcnow.side_effect = Exception('fail')
client = app_module.app.test_client()
response = client.get('/api/config')
self.assertIn(response.status_code, (200, 500))
data = response.get_json(silent=True)
# Accept either a valid config dict or an error
if data is not None and response.status_code == 500:
self.assertIn('error', data)
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -0,0 +1 @@
# ... moved and adapted code from test_phase3_endpoints.py (calendar section) ...
+77
View File
@@ -0,0 +1,77 @@
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 unittest
import tempfile
import shutil
import os
from unittest.mock import patch
from calendar_manager import CalendarManager
class TestCalendarManager(unittest.TestCase):
def setUp(self):
self.test_dir = tempfile.mkdtemp()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
os.makedirs(self.data_dir, exist_ok=True)
os.makedirs(self.config_dir, exist_ok=True)
self.manager = CalendarManager(data_dir=self.data_dir, config_dir=self.config_dir)
def tearDown(self):
shutil.rmtree(self.test_dir)
def test_initialization(self):
self.assertTrue(os.path.exists(self.manager.calendar_dir))
self.assertTrue(os.path.exists(self.manager.radicale_dir))
def test_ensure_config_exists(self):
config_file = os.path.join(self.manager.radicale_dir, 'config')
if os.path.exists(config_file):
os.remove(config_file)
self.manager._ensure_config_exists()
self.assertTrue(os.path.exists(config_file))
def test_generate_radicale_config(self):
config_file = os.path.join(self.manager.radicale_dir, 'config')
if os.path.exists(config_file):
os.remove(config_file)
self.manager._generate_radicale_config()
self.assertTrue(os.path.exists(config_file))
with open(config_file) as f:
content = f.read()
self.assertIn('[server]', content)
self.assertIn('hosts = 0.0.0.0:5232', content)
def test_get_status(self):
status = self.manager.get_status()
self.assertIsInstance(status, dict)
self.assertIn('status', status)
@patch.object(CalendarManager, 'create_calendar', return_value=True)
@patch.object(CalendarManager, 'remove_calendar', return_value=True)
def test_create_and_remove_calendar(self, mock_remove, mock_create):
result = self.manager.create_calendar('testuser', 'testcal')
self.assertTrue(result)
result = self.manager.remove_calendar('testuser', 'testcal')
self.assertTrue(result)
@patch.object(CalendarManager, 'add_event', return_value=True)
@patch.object(CalendarManager, 'remove_event', return_value=True)
def test_add_and_remove_event(self, mock_remove, mock_add):
result = self.manager.add_event('testuser', 'testcal', {'summary': 'Test'})
self.assertTrue(result)
result = self.manager.remove_event('testuser', 'testcal', 'dummyuid')
self.assertTrue(result)
def test_error_handling(self):
# Force errors by passing invalid arguments, should return False
self.assertFalse(self.manager.create_calendar(None, None))
self.assertFalse(self.manager.add_event(None, None, None))
self.assertFalse(self.manager.remove_calendar(None, None))
self.assertFalse(self.manager.remove_event(None, None, None))
if __name__ == '__main__':
unittest.main()
+169
View File
@@ -0,0 +1,169 @@
#!/usr/bin/env python3
"""
Unit tests for CellManager class
"""
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 unittest
import tempfile
import os
import json
import shutil
from unittest.mock import patch, MagicMock
from datetime import datetime
# Add parent directory to path for imports
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import CellManager
class TestCellManager(unittest.TestCase):
"""Test cases for CellManager class"""
def setUp(self):
"""Set up test environment"""
self.test_dir = tempfile.mkdtemp()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
os.makedirs(self.data_dir, exist_ok=True)
os.makedirs(self.config_dir, exist_ok=True)
# Use a unique config file for each test
self.config_path = os.path.join(self.config_dir, 'cell_config_test.json')
if os.path.exists(self.config_path):
os.remove(self.config_path)
self.env_patcher = patch.dict(os.environ, {
'CELL_NAME': 'testcell',
'DATA_DIR': self.data_dir,
'CONFIG_DIR': self.config_dir
})
self.env_patcher.start()
# Pass config_path to CellManager for isolation
self.cell_manager = CellManager(config_path=self.config_path)
def tearDown(self):
"""Clean up test environment"""
self.env_patcher.stop()
shutil.rmtree(self.test_dir)
def test_initial_config_creation(self):
"""Test that initial configuration is created correctly"""
config = self.cell_manager.config
self.assertEqual(config['cell_name'], 'testcell')
self.assertEqual(config['domain'], 'testcell.cell')
self.assertEqual(config['ip_range'], '10.0.0.0/24')
self.assertEqual(config['wireguard_port'], 51820)
self.assertIn('created_at', config)
# Only one config should exist
self.assertIsInstance(config, dict)
def test_config_persistence(self):
"""Test that configuration is saved and loaded correctly"""
# Modify config
self.cell_manager.config['cell_name'] = 'modified'
self.cell_manager.save_config()
# Create new instance to test loading
new_manager = CellManager()
self.assertEqual(new_manager.config['cell_name'], 'modified')
def test_peer_management(self):
"""Test adding and removing peers"""
# Test empty peers list
peers = self.cell_manager.get_peers()
self.assertEqual(len(peers), 0)
# Test adding peer
peer_data = {
'name': 'testpeer',
'ip': '192.168.1.100',
'public_key': 'testkey123'
}
success, message = self.cell_manager.add_peer(peer_data)
self.assertTrue(success)
self.assertIn('successfully', message)
# Test peer was added
peers = self.cell_manager.get_peers()
self.assertEqual(len(peers), 1)
self.assertEqual(peers[0]['name'], 'testpeer')
# Test adding duplicate peer (should fail)
success, message = self.cell_manager.add_peer({'name': 'testpeer', 'ip': '192.168.1.100', 'public_key': 'testkey123'})
self.assertFalse(success)
self.assertIn('already exists', message)
# Test removing peer
success, message = self.cell_manager.remove_peer('testpeer')
self.assertTrue(success)
# Test peer was removed
peers = self.cell_manager.get_peers()
self.assertEqual(len(peers), 0)
def test_peer_validation(self):
"""Test peer data validation"""
# Test missing required fields
invalid_peer = {'name': 'test'}
success, message = self.cell_manager.add_peer(invalid_peer)
self.assertFalse(success)
self.assertIn('Missing required field', message)
# Test valid peer
valid_peer = {
'name': 'testpeer',
'ip': '192.168.1.100',
'public_key': 'testkey123'
}
success, message = self.cell_manager.add_peer(valid_peer)
self.assertTrue(success)
def test_get_status(self):
"""Test status information retrieval"""
status = self.cell_manager.get_status()
self.assertIn('cell_name', status)
self.assertIn('domain', status)
self.assertIn('peers_count', status)
self.assertIn('services', status)
self.assertIn('uptime', status)
self.assertEqual(status['cell_name'], 'testcell')
self.assertEqual(status['domain'], 'testcell.cell')
self.assertEqual(status['peers_count'], 0)
@patch('subprocess.run')
def test_service_status_check(self, mock_run):
"""Test service status checking"""
mock_run.return_value.stdout = 'cell-dns\n'
mock_run.return_value.returncode = 0
services = self.cell_manager.get_services_status()
# Accept either flat or nested dict structure
if isinstance(services, dict) and 'dns' in services:
self.assertIn('dns', services)
elif 'network' in services and isinstance(services['network'], dict):
self.assertIn('status', services['network'])
# Mock failed service check
mock_run.side_effect = Exception("Docker not available")
services = self.cell_manager.get_services_status()
# Accept either flat or nested dict structure
if isinstance(services, dict) and 'dns' in services:
self.assertFalse(services['dns'])
elif 'network' in services and isinstance(services['network'], dict):
self.assertIn('status', services['network'])
def test_uptime_retrieval(self):
"""Test uptime retrieval"""
uptime = self.cell_manager.get_uptime()
self.assertIsInstance(uptime, int)
self.assertGreaterEqual(uptime, 0)
if __name__ == '__main__':
unittest.main()
+409
View File
@@ -0,0 +1,409 @@
#!/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:
# Fallback for when running from tests directory
import sys
sys.path.append('..')
from api.cell_cli import api_request, show_status, list_peers, add_peer, remove_peer, show_config, update_config
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("api.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("api.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("api.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("api.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("api.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("api.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("api.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("api.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()
+226
View File
@@ -0,0 +1,226 @@
#!/usr/bin/env python3
"""
Tests for ConfigManager
"""
import unittest
import json
import tempfile
import os
import shutil
from unittest.mock import Mock, patch, MagicMock
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))
from config_manager import ConfigManager
class TestConfigManager(unittest.TestCase):
"""Test the configuration manager functionality"""
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.config_file = os.path.join(self.temp_dir, 'cell_config.json')
self.data_dir = os.path.join(self.temp_dir, 'data')
os.makedirs(self.data_dir, exist_ok=True)
self.config_manager = ConfigManager(self.config_file, self.data_dir)
def tearDown(self):
shutil.rmtree(self.temp_dir)
def test_initialization(self):
"""Test config manager initialization"""
self.assertTrue(os.path.exists(self.config_file))
self.assertTrue(os.path.exists(self.data_dir))
self.assertTrue(os.path.exists(self.config_manager.backup_dir))
self.assertIsNotNone(self.config_manager.service_schemas)
def test_get_service_config(self):
"""Test getting service configuration"""
# Test with non-existent service
with self.assertRaises(ValueError):
self.config_manager.get_service_config('nonexistent_service')
# Test with valid service
config = self.config_manager.get_service_config('network')
self.assertEqual(config, {})
def test_update_service_config(self):
"""Test updating service configuration"""
test_config = {
'dns_port': 53,
'dhcp_range': '10.0.0.100-10.0.0.200',
'ntp_servers': ['pool.ntp.org']
}
success = self.config_manager.update_service_config('network', test_config)
self.assertTrue(success)
# Verify config was saved
config = self.config_manager.get_service_config('network')
self.assertEqual(config['dns_port'], 53)
self.assertEqual(config['dhcp_range'], '10.0.0.100-10.0.0.200')
self.assertEqual(config['ntp_servers'], ['pool.ntp.org'])
def test_validate_config(self):
"""Test configuration validation"""
# Test valid config
valid_config = {
'dns_port': 53,
'dhcp_range': '10.0.0.100-10.0.0.200',
'ntp_servers': ['pool.ntp.org']
}
validation = self.config_manager.validate_config('network', valid_config)
self.assertTrue(validation['valid'])
self.assertEqual(len(validation['errors']), 0)
# Test invalid config (missing required field)
invalid_config = {
'dns_port': 53,
'ntp_servers': ['pool.ntp.org']
# Missing dhcp_range
}
validation = self.config_manager.validate_config('network', invalid_config)
self.assertFalse(validation['valid'])
self.assertGreater(len(validation['errors']), 0)
def test_backup_and_restore(self):
"""Test backup and restore functionality"""
# Create some test configs
test_config = {
'dns_port': 53,
'dhcp_range': '10.0.0.100-10.0.0.200',
'ntp_servers': ['pool.ntp.org']
}
self.config_manager.update_service_config('network', test_config)
# Create backup
backup_id = self.config_manager.backup_config()
self.assertIsNotNone(backup_id)
# List backups
backups = self.config_manager.list_backups()
self.assertIsInstance(backups, list)
self.assertGreater(len(backups), 0)
# Modify config
modified_config = {
'dns_port': 5353,
'dhcp_range': '10.0.0.100-10.0.0.200',
'ntp_servers': ['pool.ntp.org']
}
self.config_manager.update_service_config('network', modified_config)
# Restore backup
success = self.config_manager.restore_config(backup_id)
self.assertTrue(success)
# Verify restoration
config = self.config_manager.get_service_config('network')
self.assertEqual(config['dns_port'], 53) # Should be restored value
def test_export_import_config(self):
"""Test export and import functionality"""
# Create test configs
test_configs = {
'network': {
'dns_port': 53,
'dhcp_range': '10.0.0.100-10.0.0.200',
'ntp_servers': ['pool.ntp.org']
},
'wireguard': {
'port': 51820,
'private_key': 'test_key',
'address': '10.0.0.1/24'
}
}
for service, config in test_configs.items():
self.config_manager.update_service_config(service, config)
# Export config
exported = self.config_manager.export_config()
self.assertIsInstance(exported, str)
# Import config
success = self.config_manager.import_config(exported)
self.assertTrue(success)
# Verify import
for service, expected_config in test_configs.items():
config = self.config_manager.get_service_config(service)
for key, value in expected_config.items():
self.assertEqual(config[key], value)
def test_get_all_configs(self):
"""Test getting all configurations"""
# Create some test configs
test_configs = {
'network': {'dns_port': 53, 'dhcp_range': '10.0.0.100-10.0.0.200', 'ntp_servers': ['pool.ntp.org']},
'wireguard': {'port': 51820}
}
for service, config in test_configs.items():
self.config_manager.update_service_config(service, config)
all_configs = self.config_manager.get_all_configs()
self.assertIn('network', all_configs)
self.assertIn('wireguard', all_configs)
self.assertEqual(all_configs['network']['dns_port'], 53)
def test_get_config_summary(self):
"""Test getting configuration summary"""
# Create some test configs
test_configs = {
'network': {'dns_port': 53, 'dhcp_range': '10.0.0.100-10.0.0.200', 'ntp_servers': ['pool.ntp.org']},
'wireguard': {'port': 51820}
}
for service, config in test_configs.items():
self.config_manager.update_service_config(service, config)
summary = self.config_manager.get_config_summary()
self.assertIn('total_services', summary)
self.assertIn('configured_services', summary)
self.assertIn('backup_count', summary)
def test_get_config_hash(self):
"""Test getting configuration hash"""
test_config = {'dns_port': 53, 'dhcp_range': '10.0.0.100-10.0.0.200', 'ntp_servers': ['pool.ntp.org']}
self.config_manager.update_service_config('network', test_config)
hash1 = self.config_manager.get_config_hash('network')
self.assertIsInstance(hash1, str)
self.assertGreater(len(hash1), 0)
# Update config and get new hash
test_config['dns_port'] = 5353
self.config_manager.update_service_config('network', test_config)
hash2 = self.config_manager.get_config_hash('network')
self.assertNotEqual(hash1, hash2)
def test_has_config_changed(self):
"""Test checking if configuration has changed"""
test_config = {'dns_port': 53, 'dhcp_range': '10.0.0.100-10.0.0.200', 'ntp_servers': ['pool.ntp.org']}
self.config_manager.update_service_config('network', test_config)
original_hash = self.config_manager.get_config_hash('network')
# Check if changed (should be False since we just set it)
changed = self.config_manager.has_config_changed('network', original_hash)
self.assertFalse(changed)
# Update config
test_config['dns_port'] = 5353
self.config_manager.update_service_config('network', test_config)
# Check if changed (should be True)
changed = self.config_manager.has_config_changed('network', original_hash)
self.assertTrue(changed)
if __name__ == '__main__':
unittest.main()
+49
View File
@@ -0,0 +1,49 @@
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 unittest
from unittest.mock import patch, MagicMock
from container_manager import ContainerManager
class TestContainerManager(unittest.TestCase):
@patch('docker.from_env')
def test_list_containers(self, mock_from_env):
mock_client = MagicMock()
mock_container = MagicMock()
mock_container.id = 'abc'
mock_container.name = 'test'
mock_container.status = 'running'
mock_container.image.tags = ['img']
mock_container.labels = {}
mock_client.containers.list.return_value = [mock_container]
mock_from_env.return_value = mock_client
mgr = ContainerManager()
result = mgr.list_containers()
self.assertEqual(result[0]['name'], 'test')
@patch('docker.from_env')
def test_start_stop_restart_container(self, mock_from_env):
mock_client = MagicMock()
mock_container = MagicMock()
mock_client.containers.get.return_value = mock_container
mock_from_env.return_value = mock_client
mgr = ContainerManager()
# Start
self.assertTrue(mgr.start_container('test'))
mock_container.start.assert_called_once()
# Stop
self.assertTrue(mgr.stop_container('test'))
mock_container.stop.assert_called_once()
# Restart
self.assertTrue(mgr.restart_container('test'))
mock_container.restart.assert_called_once()
# Exception cases
mock_client.containers.get.side_effect = Exception('fail')
self.assertFalse(mgr.start_container('bad'))
self.assertFalse(mgr.stop_container('bad'))
self.assertFalse(mgr.restart_container('bad'))
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -0,0 +1 @@
# ... moved and adapted code from test_phase3_endpoints.py (email section) ...
+108
View File
@@ -0,0 +1,108 @@
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 unittest
import tempfile
import shutil
import os
from unittest.mock import patch, MagicMock
from email_manager import EmailManager
class TestEmailManager(unittest.TestCase):
def setUp(self):
self.test_dir = tempfile.mkdtemp()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
os.makedirs(self.data_dir, exist_ok=True)
os.makedirs(self.config_dir, exist_ok=True)
self.manager = EmailManager(data_dir=self.data_dir, config_dir=self.config_dir)
def tearDown(self):
shutil.rmtree(self.test_dir)
def test_initialization(self):
self.assertTrue(os.path.exists(self.manager.email_dir))
self.assertTrue(os.path.exists(self.manager.postfix_dir))
self.assertTrue(os.path.exists(self.manager.dovecot_dir))
@patch.object(EmailManager, '_reload_email_services', return_value=True)
def test_create_and_delete_email_user(self, mock_reload):
result = self.manager.create_email_user('testuser', 'testdomain', 'password')
self.assertTrue(result)
result = self.manager.delete_email_user('testuser', 'testdomain')
self.assertTrue(result)
def test_list_email_users_empty(self):
users = self.manager.list_email_users()
self.assertIsInstance(users, list)
self.assertEqual(len(users), 0)
@patch('smtplib.SMTP')
def test_send_email(self, mock_smtp):
instance = mock_smtp.return_value.__enter__.return_value
instance.sendmail.return_value = {}
result = self.manager.send_email('from@cell', 'to@cell', 'Subject', 'Body')
self.assertTrue(result)
# Simulate error by raising in SMTP constructor
mock_smtp.side_effect = Exception('SMTP error')
result = self.manager.send_email('from@cell', 'to@cell', 'Subject', 'Body')
self.assertFalse(result)
@patch('imaplib.IMAP4_SSL')
def test_get_mailbox_info(self, mock_imap):
instance = mock_imap.return_value
instance.login.return_value = 'OK'
instance.select.return_value = ('OK', [b'1'])
instance.search.return_value = ('OK', [b'1 2 3'])
instance.fetch.return_value = ('OK', [(b'1', b'RFC822')])
info = self.manager.get_mailbox_info('testuser', 'testdomain')
self.assertIsInstance(info, dict)
instance.login.side_effect = Exception('IMAP error')
info = self.manager.get_mailbox_info('testuser', 'testdomain')
self.assertIn('error', info)
@patch('subprocess.run')
def test_get_email_status(self, mock_run):
mock_run.return_value.stdout = 'cell-mail\n'
mock_run.return_value.returncode = 0
status = self.manager.get_email_status()
self.assertIsInstance(status, dict)
self.assertIn('postfix_running', status)
self.assertIn('dovecot_running', status)
@patch('requests.get')
def test_test_email_connectivity(self, mock_get):
mock_get.return_value.status_code = 200
result = self.manager.test_email_connectivity()
self.assertIsInstance(result, dict)
mock_get.side_effect = Exception('HTTP error')
result = self.manager.test_email_connectivity()
self.assertIn('smtp', result)
@patch('subprocess.run')
def test_get_email_logs(self, mock_run):
mock_run.return_value.stdout = 'log line\n'
mock_run.return_value.returncode = 0
logs = self.manager.get_email_logs('all', 10)
self.assertIsInstance(logs, dict)
self.assertIn('postfix', logs)
self.assertIn('dovecot', logs)
def test_get_status(self):
status = self.manager.get_status()
self.assertIsInstance(status, dict)
self.assertIn('status', status)
def test_error_handling(self):
# Force errors by passing invalid arguments, should return False or error dict
self.assertFalse(self.manager.create_email_user(None, None, None))
self.assertFalse(self.manager.delete_email_user(None, None))
self.assertFalse(self.manager.send_email(None, None, None, None))
info = self.manager.get_mailbox_info(None, None)
self.assertIn('error', info)
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -0,0 +1 @@
# ... moved and adapted code from test_phase3_endpoints.py (file section) ...
+120
View File
@@ -0,0 +1,120 @@
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 unittest
import tempfile
import shutil
import os
from unittest.mock import patch, MagicMock
from file_manager import FileManager
class TestFileManager(unittest.TestCase):
def setUp(self):
self.test_dir = tempfile.mkdtemp()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
os.makedirs(self.data_dir, exist_ok=True)
os.makedirs(self.config_dir, exist_ok=True)
self.manager = FileManager(data_dir=self.data_dir, config_dir=self.config_dir)
def tearDown(self):
shutil.rmtree(self.test_dir)
def test_initialization(self):
self.assertTrue(os.path.exists(self.manager.files_dir))
self.assertTrue(os.path.exists(self.manager.webdav_dir))
def test_create_and_delete_user(self):
result = self.manager.create_user('testuser', 'password')
self.assertTrue(result)
result = self.manager.delete_user('testuser')
self.assertTrue(result)
def test_list_users_empty(self):
users = self.manager.list_users()
self.assertIsInstance(users, list)
self.assertEqual(len(users), 0)
def test_create_and_delete_folder(self):
self.manager.create_user('testuser', 'password')
result = self.manager.create_folder('testuser', 'TestFolder')
self.assertTrue(result)
result = self.manager.delete_folder('testuser', 'TestFolder')
self.assertTrue(result)
def test_upload_download_delete_file(self):
self.manager.create_user('testuser', 'password')
self.manager.create_folder('testuser', 'TestFolder')
file_data = b'Hello, world!'
result = self.manager.upload_file('testuser', 'TestFolder/hello.txt', file_data)
self.assertTrue(result)
downloaded = self.manager.download_file('testuser', 'TestFolder/hello.txt')
self.assertEqual(downloaded, file_data)
result = self.manager.delete_file('testuser', 'TestFolder/hello.txt')
self.assertTrue(result)
def test_list_files(self):
self.manager.create_user('testuser', 'password')
self.manager.create_folder('testuser', 'TestFolder')
self.manager.upload_file('testuser', 'TestFolder/hello.txt', b'abc')
files = self.manager.list_files('testuser', 'TestFolder')
self.assertIsInstance(files, list)
self.assertEqual(len(files), 1)
self.assertEqual(files[0]['name'], 'hello.txt')
@patch('subprocess.run')
def test_get_webdav_status(self, mock_run):
mock_run.return_value.stdout = 'cell-webdav\n'
mock_run.return_value.returncode = 0
status = self.manager.get_webdav_status()
self.assertIsInstance(status, dict)
self.assertIn('webdav_running', status)
@patch('requests.get')
def test_test_webdav_connectivity(self, mock_get):
mock_get.return_value.status_code = 200
result = self.manager.test_webdav_connectivity()
self.assertIsInstance(result, dict)
mock_get.side_effect = Exception('HTTP error')
result = self.manager.test_webdav_connectivity()
self.assertIn('http', result)
@patch('subprocess.run')
def test_get_webdav_logs(self, mock_run):
mock_run.return_value.stdout = 'log line\n'
mock_run.return_value.returncode = 0
logs = self.manager.get_webdav_logs(10)
self.assertIsInstance(logs, str)
self.assertIn('log line', logs)
def test_backup_and_restore_user_files(self):
self.manager.create_user('testuser', 'password')
backup_path = os.path.join(self.test_dir, 'backup.zip')
result = self.manager.backup_user_files('testuser', backup_path)
self.assertTrue(result)
result = self.manager.restore_user_files('testuser', backup_path)
self.assertTrue(result)
def test_get_status(self):
status = self.manager.get_status()
self.assertIsInstance(status, dict)
self.assertIn('status', status)
def test_error_handling(self):
# Force errors by passing invalid arguments, should return False or empty/None
self.assertFalse(self.manager.create_user('', ''))
self.assertFalse(self.manager.delete_user(''))
self.assertFalse(self.manager.create_folder('', ''))
self.assertFalse(self.manager.delete_folder('', ''))
self.assertFalse(self.manager.upload_file('', '', b''))
self.assertIsNone(self.manager.download_file('', ''))
self.assertFalse(self.manager.delete_file('', ''))
self.assertEqual(self.manager.list_files(''), [])
self.assertFalse(self.manager.backup_user_files('', ''))
self.assertFalse(self.manager.restore_user_files('', ''))
if __name__ == '__main__':
unittest.main()
+295
View File
@@ -0,0 +1,295 @@
#!/usr/bin/env python3
"""
Integration Tests for Components
"""
import unittest
import json
import tempfile
import os
import shutil
import time
from unittest.mock import Mock, patch, MagicMock
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))
from base_service_manager import BaseServiceManager
from config_manager import ConfigManager
from service_bus import ServiceBus, EventType
from log_manager import LogManager
from network_manager import NetworkManager
class TestIntegration(unittest.TestCase):
"""Test integration between components"""
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.data_dir = os.path.join(self.temp_dir, 'data')
self.config_dir = os.path.join(self.temp_dir, 'config')
self.log_dir = os.path.join(self.temp_dir, 'logs')
os.makedirs(self.data_dir, exist_ok=True)
os.makedirs(self.config_dir, exist_ok=True)
os.makedirs(self.log_dir, exist_ok=True)
# Initialize components
self.config_manager = ConfigManager(self.config_dir, self.data_dir)
self.service_bus = ServiceBus()
self.log_manager = LogManager(log_dir=self.log_dir)
# Create a test service manager
class TestServiceManager(BaseServiceManager):
def get_status(self):
return {'running': True, 'status': 'online'}
def test_connectivity(self):
return {'success': True, 'message': 'Connected'}
self.test_service = TestServiceManager('test_service', self.data_dir, self.config_dir)
def tearDown(self):
self.log_manager.stop()
self.service_bus.stop()
shutil.rmtree(self.temp_dir)
def test_service_bus_with_config_manager(self):
"""Test service bus integration with config manager"""
# Register config manager with service bus
self.service_bus.register_service('config_manager', self.config_manager)
# Test calling config manager through service bus
test_config = {
'dns_port': 53,
'dhcp_range': '10.0.0.100-10.0.0.200',
'ntp_servers': ['pool.ntp.org']
}
# Update config through service bus
result = self.service_bus.call_service(
'config_manager',
'update_service_config',
service='network',
config=test_config
)
self.assertTrue(result)
# Get config through service bus
config = self.service_bus.call_service(
'config_manager',
'get_service_config',
service='network'
)
self.assertEqual(config['dns_port'], 53)
def test_service_bus_with_log_manager(self):
"""Test service bus integration with log manager"""
# Register log manager with service bus
self.service_bus.register_service('log_manager', self.log_manager)
# Add service logger through service bus
config = {'level': 'INFO', 'formatter': 'json'}
result = self.service_bus.call_service(
'log_manager',
'add_service_logger',
service='test_service',
config=config
)
# Get logs through service bus
logs = self.service_bus.call_service(
'log_manager',
'get_service_logs',
service='test_service'
)
self.assertIsInstance(logs, list)
def test_event_driven_config_updates(self):
"""Test event-driven configuration updates"""
config_updates = []
def config_change_handler(event):
config_updates.append(event.data)
# Subscribe to config change events
self.service_bus.subscribe_to_event(EventType.CONFIG_CHANGED, config_change_handler)
# Update config with valid configuration
test_config = {
'dns_port': 5353,
'dhcp_range': '10.0.0.100-10.0.0.200',
'ntp_servers': ['pool.ntp.org']
}
self.config_manager.update_service_config('network', test_config)
# Publish config change event
self.service_bus.publish_event(
EventType.CONFIG_CHANGED,
'config_manager',
{'service': 'network', 'config': test_config}
)
# Give time for event processing
time.sleep(0.1)
# Check if event was received
self.assertIsInstance(config_updates, list)
# Note: Event processing might be async, so we can't guarantee immediate reception
def test_service_lifecycle_with_logging(self):
"""Test service lifecycle with integrated logging"""
# Add service logger
self.log_manager.add_service_logger('test_service', {'level': 'INFO'})
# Register service with bus
self.service_bus.register_service('test_service', self.test_service)
# Start service bus
self.service_bus.start()
# Test service operations
status = self.service_bus.call_service('test_service', 'get_status')
self.assertEqual(status['running'], True)
# Check if logs were generated
logs = self.log_manager.get_service_logs('test_service')
self.assertIsInstance(logs, list)
# Stop service bus
self.service_bus.stop()
def test_network_manager_inheritance(self):
"""Test NetworkManager inheritance from BaseServiceManager"""
network_manager = NetworkManager(self.data_dir, self.config_dir)
# Test inherited methods
status = network_manager.get_status()
self.assertIn('running', status)
self.assertIn('status', status)
connectivity = network_manager.test_connectivity()
self.assertIn('success', connectivity)
# Test network-specific methods
self.assertTrue(hasattr(network_manager, 'get_dns_status'))
def test_comprehensive_workflow(self):
"""Test comprehensive workflow with all components"""
# Start all components
self.service_bus.start()
# Register all services
self.service_bus.register_service('config_manager', self.config_manager)
self.service_bus.register_service('log_manager', self.log_manager)
self.service_bus.register_service('test_service', self.test_service)
# Add logging for all services
self.log_manager.add_service_logger('config_manager', {'level': 'INFO'})
self.log_manager.add_service_logger('log_manager', {'level': 'INFO'})
self.log_manager.add_service_logger('test_service', {'level': 'INFO'})
# Perform workflow
# 1. Update configuration
test_config = {
'dns_port': 53,
'dhcp_range': '10.0.0.100-10.0.0.200',
'ntp_servers': ['pool.ntp.org']
}
success = self.service_bus.call_service(
'config_manager',
'update_service_config',
service='network',
config=test_config
)
self.assertTrue(success)
# 2. Check service status
status = self.service_bus.call_service('test_service', 'get_status')
self.assertEqual(status['running'], True)
# 3. Get logs
logs = self.service_bus.call_service(
'log_manager',
'get_service_logs',
service='test_service'
)
self.assertIsInstance(logs, list)
# 4. Get configuration
config = self.service_bus.call_service(
'config_manager',
'get_service_config',
service='network'
)
self.assertEqual(config['dns_port'], 53)
# Stop all components
self.service_bus.stop()
def test_error_propagation(self):
"""Test error propagation through components"""
# Register services
self.service_bus.register_service('config_manager', self.config_manager)
self.service_bus.register_service('log_manager', self.log_manager)
# Add logging
self.log_manager.add_service_logger('config_manager', {'level': 'ERROR'})
# Test error handling
with self.assertRaises(ValueError):
self.service_bus.call_service(
'config_manager',
'get_service_config',
service='nonexistent_service'
)
# Check if error was logged
logs = self.log_manager.get_service_logs('config_manager', level='ERROR')
self.assertIsInstance(logs, list)
def test_component_initialization_order(self):
"""Test proper component initialization order"""
# Test that components can be initialized in any order
components = []
# Initialize in different orders
components.append(ConfigManager(self.config_dir, self.data_dir))
components.append(ServiceBus())
components.append(LogManager(log_dir=self.log_dir))
# Verify all components are properly initialized
for component in components:
self.assertIsNotNone(component)
# Clean up
for component in components:
if hasattr(component, 'stop'):
component.stop()
def test_memory_cleanup(self):
"""Test proper memory cleanup"""
# Create components
config_manager = ConfigManager(self.config_dir, self.data_dir)
service_bus = ServiceBus()
log_manager = LogManager(log_dir=self.log_dir)
# Register services
service_bus.register_service('config_manager', config_manager)
service_bus.register_service('log_manager', log_manager)
# Start services
service_bus.start()
# Stop services
service_bus.stop()
log_manager.stop()
# Verify cleanup (no exceptions should be raised)
self.assertTrue(True)
if __name__ == '__main__':
unittest.main()
+247
View File
@@ -0,0 +1,247 @@
#!/usr/bin/env python3
"""
Tests for LogManager
"""
import unittest
import json
import tempfile
import os
import shutil
import time
from datetime import datetime, timedelta
from unittest.mock import Mock, patch, MagicMock
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))
from log_manager import LogManager, LogLevel
class TestLogManager(unittest.TestCase):
"""Test the log manager functionality"""
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
self.log_dir = os.path.join(self.temp_dir, 'logs')
os.makedirs(self.log_dir, exist_ok=True)
self.log_manager = LogManager(log_dir=self.log_dir)
# Add this helper to ensure log dir exists before logger usage
def ensure_log_dir(self):
os.makedirs(self.log_dir, exist_ok=True)
# In each test that uses logger, call self.ensure_log_dir() before logger usage
def test_initialization(self):
"""Test log manager initialization"""
self.assertTrue(os.path.exists(self.log_dir))
self.assertIsNotNone(self.log_manager.formatters)
self.assertIsNotNone(self.log_manager.handlers)
self.assertIsNotNone(self.log_manager.log_stats)
def test_add_service_logger(self):
self.ensure_log_dir()
"""Test adding service logger"""
config = {
'level': 'INFO',
'formatter': 'json',
'console': True
}
self.log_manager.add_service_logger('test_service', config)
self.assertIn('test_service', self.log_manager.service_loggers)
self.assertIn('test_service', self.log_manager.handlers)
def test_get_service_logs(self):
self.ensure_log_dir()
"""Test getting service logs"""
# Add service logger
self.log_manager.add_service_logger('test_service', {'level': 'INFO'})
# Create some log entries
logger = self.log_manager.service_loggers['test_service']
logger.info("Test log message 1")
logger.warning("Test log message 2")
logger.error("Test log message 3")
# Get logs
logs = self.log_manager.get_service_logs('test_service', lines=3)
self.assertIsInstance(logs, list)
# Note: We can't guarantee exact count due to async logging
def test_search_logs(self):
self.ensure_log_dir()
"""Test log search functionality"""
# Add service logger
self.log_manager.add_service_logger('test_service', {'level': 'INFO'})
# Create some log entries
logger = self.log_manager.service_loggers['test_service']
logger.info("User login successful")
logger.info("Database connection established")
logger.error("Authentication failed")
# Search logs
results = self.log_manager.search_logs('login')
self.assertIsInstance(results, list)
# Search with time range
end_time = datetime.now()
start_time = end_time - timedelta(hours=1)
results = self.log_manager.search_logs(
'login',
time_range=(start_time, end_time)
)
self.assertIsInstance(results, list)
# Search with service filter
results = self.log_manager.search_logs(
'login',
services=['test_service']
)
self.assertIsInstance(results, list)
def test_export_logs(self):
self.ensure_log_dir()
"""Test log export functionality"""
# Add service logger
self.log_manager.add_service_logger('test_service', {'level': 'INFO'})
# Create some log entries
logger = self.log_manager.service_loggers['test_service']
logger.info("Export test message 1")
logger.info("Export test message 2")
# Export as JSON
json_export = self.log_manager.export_logs('json')
self.assertIsInstance(json_export, str)
# Export as CSV
csv_export = self.log_manager.export_logs('csv')
self.assertIsInstance(csv_export, str)
# Export as text
text_export = self.log_manager.export_logs('text')
self.assertIsInstance(text_export, str)
def test_log_statistics(self):
self.ensure_log_dir()
"""Test log statistics functionality"""
# Add service logger
self.log_manager.add_service_logger('test_service', {'level': 'INFO'})
# Create some log entries
logger = self.log_manager.service_loggers['test_service']
logger.info("Info message")
logger.warning("Warning message")
logger.error("Error message")
# Get statistics
stats = self.log_manager.get_log_statistics()
self.assertIsInstance(stats, dict)
# Get service-specific statistics
service_stats = self.log_manager.get_log_statistics('test_service')
self.assertIsInstance(service_stats, dict)
def test_log_rotation(self):
self.ensure_log_dir()
"""Test log rotation functionality"""
# Add service logger with small max file size
config = {
'level': 'INFO',
'formatter': 'text'
}
self.log_manager.add_service_logger('test_service', config)
# Create many log entries to trigger rotation
logger = self.log_manager.service_loggers['test_service']
for i in range(1000):
logger.info(f"Log entry {i}: " + "x" * 100) # Large log entries
# Trigger rotation
self.log_manager.rotate_logs('test_service')
# Check if rotation files exist
log_file = os.path.join(self.log_dir, 'test_service.log')
self.assertTrue(os.path.exists(log_file))
def test_cleanup_old_logs(self):
self.ensure_log_dir()
"""Test cleanup of old logs"""
# Add service logger
self.log_manager.add_service_logger('test_service', {'level': 'INFO'})
# Create some log entries
logger = self.log_manager.service_loggers['test_service']
logger.info("Test message")
# Cleanup old logs (should not affect recent logs)
self.log_manager.cleanup_old_logs(days=1)
# Verify logs still exist
logs = self.log_manager.get_service_logs('test_service')
self.assertIsInstance(logs, list)
def test_log_file_info(self):
self.ensure_log_dir()
"""Test log file information"""
# Add service logger
self.log_manager.add_service_logger('test_service', {'level': 'INFO'})
# Create some log entries
logger = self.log_manager.service_loggers['test_service']
logger.info("Test message")
# Get log file info
info = self.log_manager.get_log_file_info('test_service')
self.assertIsInstance(info, dict)
self.assertIn('file_path', info)
self.assertIn('exists', info)
def test_compress_old_logs(self):
self.ensure_log_dir()
"""Test compression of old logs"""
# Add service logger
self.log_manager.add_service_logger('test_service', {'level': 'INFO'})
# Create some log entries
logger = self.log_manager.service_loggers['test_service']
logger.info("Test message for compression")
# Compress old logs
self.log_manager.compress_old_logs()
# Verify compression worked (should not raise errors)
self.assertTrue(True) # If we get here, compression worked
def test_formatters(self):
self.ensure_log_dir()
"""Test different log formatters"""
# Test JSON formatter
json_logger = self.log_manager.add_service_logger('json_service', {
'level': 'INFO',
'formatter': 'json'
})
# Test text formatter
text_logger = self.log_manager.add_service_logger('text_service', {
'level': 'INFO',
'formatter': 'text'
})
# Test detailed formatter
detailed_logger = self.log_manager.add_service_logger('detailed_service', {
'level': 'INFO',
'formatter': 'detailed'
})
# Verify formatters exist
self.assertIn('json', self.log_manager.formatters)
self.assertIn('text', self.log_manager.formatters)
self.assertIn('detailed', self.log_manager.formatters)
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -0,0 +1 @@
# ... moved and adapted code from test_phase1_endpoints.py ...
+276
View File
@@ -0,0 +1,276 @@
#!/usr/bin/env python3
"""
Unit tests for NetworkManager class
"""
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 unittest
import tempfile
import os
import json
import shutil
from unittest.mock import patch, MagicMock
from datetime import datetime
# Add parent directory to path for imports
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from network_manager import NetworkManager
class TestNetworkManager(unittest.TestCase):
"""Test cases for NetworkManager class"""
def setUp(self):
"""Set up test environment"""
self.test_dir = tempfile.mkdtemp()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
os.makedirs(self.data_dir, exist_ok=True)
os.makedirs(self.config_dir, exist_ok=True)
# Create NetworkManager instance
self.network_manager = NetworkManager(self.data_dir, self.config_dir)
def tearDown(self):
"""Clean up test environment"""
shutil.rmtree(self.test_dir)
def test_initialization(self):
"""Test NetworkManager initialization"""
self.assertEqual(self.network_manager.data_dir, self.data_dir)
self.assertEqual(self.network_manager.config_dir, self.config_dir)
self.assertTrue(os.path.exists(self.network_manager.dns_zones_dir))
self.assertTrue(os.path.exists(os.path.dirname(self.network_manager.dhcp_leases_file)))
def test_generate_zone_content(self):
"""Test DNS zone content generation"""
records = [
{'name': 'test1', 'type': 'A', 'value': '192.168.1.10', 'ttl': 3600},
{'name': 'test2', 'type': 'CNAME', 'value': 'test1', 'ttl': 1800}
]
content = self.network_manager._generate_zone_content('test.cell', records)
self.assertIn('test.cell', content)
self.assertIn('SOA', content)
self.assertIn('192.168.1.10', content)
self.assertIn('test1', content)
self.assertIn('CNAME', content)
def test_add_dns_record(self):
"""Test adding DNS record"""
success = self.network_manager.add_dns_record('test.cell', 'test', 'A', '192.168.1.100')
self.assertTrue(success)
# Check if zone file was created
zone_file = os.path.join(self.network_manager.dns_zones_dir, 'test.cell.zone')
self.assertTrue(os.path.exists(zone_file))
# Check content
with open(zone_file, 'r') as f:
content = f.read()
self.assertIn('test', content)
self.assertIn('192.168.1.100', content)
def test_remove_dns_record(self):
"""Test removing DNS record"""
# Add a record first
self.network_manager.add_dns_record('test.cell', 'test', 'A', '192.168.1.100')
# Remove it
success = self.network_manager.remove_dns_record('test.cell', 'test', 'A')
self.assertTrue(success)
# Check if record was removed
zone_file = os.path.join(self.network_manager.dns_zones_dir, 'test.cell.zone')
with open(zone_file, 'r') as f:
content = f.read()
self.assertNotIn('192.168.1.100', content)
def test_load_dns_records(self):
"""Test loading DNS records from zone file"""
# Create a test zone file
zone_file = os.path.join(self.network_manager.dns_zones_dir, 'test.cell.zone')
content = """$TTL 3600
@ IN SOA test.cell. admin.test.cell. (
2024010101 ; Serial
3600 ; Refresh
1800 ; Retry
1209600 ; Expire
3600 ; Minimum TTL
)
; Name servers
@ IN NS test.cell.
test1 3600 IN A 192.168.1.10
test2 1800 IN CNAME test1
"""
with open(zone_file, 'w') as f:
f.write(content)
records = self.network_manager._load_dns_records('test.cell')
self.assertEqual(len(records), 2)
self.assertEqual(records[0]['name'], 'test1')
self.assertEqual(records[0]['value'], '192.168.1.10')
self.assertEqual(records[1]['name'], 'test2')
self.assertEqual(records[1]['type'], 'CNAME')
def test_get_dhcp_leases(self):
"""Test getting DHCP leases"""
# Create a test leases file
leases_file = self.network_manager.dhcp_leases_file
content = """1234567890 aa:bb:cc:dd:ee:ff 192.168.1.100 testhost *
1234567891 11:22:33:44:55:66 192.168.1.101 anotherhost *
"""
with open(leases_file, 'w') as f:
f.write(content)
leases = self.network_manager.get_dhcp_leases()
self.assertEqual(len(leases), 2)
self.assertEqual(leases[0]['mac'], 'aa:bb:cc:dd:ee:ff')
self.assertEqual(leases[0]['ip'], '192.168.1.100')
self.assertEqual(leases[0]['hostname'], 'testhost')
self.assertEqual(leases[1]['mac'], '11:22:33:44:55:66')
self.assertEqual(leases[1]['ip'], '192.168.1.101')
def test_add_dhcp_reservation(self):
"""Test adding DHCP reservation"""
success = self.network_manager.add_dhcp_reservation('aa:bb:cc:dd:ee:ff', '192.168.1.100', 'testhost')
self.assertTrue(success)
# Check if reservation file was created
reservation_file = os.path.join(self.config_dir, 'dhcp', 'reservations.conf')
self.assertTrue(os.path.exists(reservation_file))
# Check content
with open(reservation_file, 'r') as f:
content = f.read()
self.assertIn('aa:bb:cc:dd:ee:ff', content)
self.assertIn('192.168.1.100', content)
self.assertIn('testhost', content)
def test_remove_dhcp_reservation(self):
"""Test removing DHCP reservation"""
# Add a reservation first
self.network_manager.add_dhcp_reservation('aa:bb:cc:dd:ee:ff', '192.168.1.100', 'testhost')
# Remove it
success = self.network_manager.remove_dhcp_reservation('aa:bb:cc:dd:ee:ff')
self.assertTrue(success)
# Check if reservation was removed
reservation_file = os.path.join(self.config_dir, 'dhcp', 'reservations.conf')
with open(reservation_file, 'r') as f:
content = f.read()
self.assertNotIn('aa:bb:cc:dd:ee:ff', content)
@patch('subprocess.run')
def test_get_ntp_status(self, mock_run):
"""Test getting NTP status"""
# Mock NTP service running
mock_run.return_value.stdout = 'cell-ntp\n'
mock_run.return_value.returncode = 0
status = self.network_manager.get_ntp_status()
self.assertTrue(status['running'])
self.assertIn('stats', status)
@patch('subprocess.run')
def test_get_ntp_status_not_running(self, mock_run):
"""Test getting NTP status when service is not running"""
# Mock NTP service not running
mock_run.return_value.stdout = ''
mock_run.return_value.returncode = 0
status = self.network_manager.get_ntp_status()
self.assertFalse(status['running'])
self.assertIn('stats', status)
@patch('subprocess.run')
def test_test_dns_resolution(self, mock_run):
"""Test DNS resolution testing"""
# Mock successful DNS resolution
mock_run.return_value.returncode = 0
mock_run.return_value.stdout = 'test.cell -> 192.168.1.100'
mock_run.return_value.stderr = ''
result = self.network_manager.test_dns_resolution('test.cell')
self.assertTrue(result['success'])
self.assertIn('192.168.1.100', result['output'])
@patch('subprocess.run')
def test_test_dns_resolution_failure(self, mock_run):
"""Test DNS resolution testing with failure"""
# Mock failed DNS resolution
mock_run.return_value.returncode = 1
mock_run.return_value.stdout = ''
mock_run.return_value.stderr = 'NXDOMAIN'
result = self.network_manager.test_dns_resolution('nonexistent.cell')
self.assertFalse(result['success'])
self.assertIn('NXDOMAIN', result['error'])
@patch('subprocess.run')
def test_test_dhcp_functionality(self, mock_run):
"""Test DHCP functionality testing"""
# Mock DHCP service running
mock_run.return_value.stdout = 'cell-dhcp\n'
mock_run.return_value.returncode = 0
result = self.network_manager.test_dhcp_functionality()
self.assertTrue(result['running'])
self.assertIn('leases_count', result)
self.assertIn('leases', result)
@patch('subprocess.run')
def test_test_ntp_functionality(self, mock_run):
"""Test NTP functionality testing"""
# Mock NTP service running with tracking
mock_run.return_value.stdout = 'cell-ntp\n'
mock_run.return_value.returncode = 0
result = self.network_manager.test_ntp_functionality()
self.assertTrue(result['running'])
self.assertIn('ntp_test', result)
def test_update_dns_zone(self):
"""Test updating DNS zone"""
records = [
{'name': 'test1', 'type': 'A', 'value': '192.168.1.10', 'ttl': 3600},
{'name': 'test2', 'type': 'A', 'value': '192.168.1.11', 'ttl': 3600}
]
success = self.network_manager.update_dns_zone('test.cell', records)
self.assertTrue(success)
# Check if zone file was created
zone_file = os.path.join(self.network_manager.dns_zones_dir, 'test.cell.zone')
self.assertTrue(os.path.exists(zone_file))
# Check content
with open(zone_file, 'r') as f:
content = f.read()
self.assertIn('test1', content)
self.assertIn('test2', content)
self.assertIn('192.168.1.10', content)
self.assertIn('192.168.1.11', content)
if __name__ == '__main__':
unittest.main()
+81
View File
@@ -0,0 +1,81 @@
import unittest
import tempfile
import shutil
import os
import json
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))
from peer_registry import PeerRegistry
class TestPeerRegistry(unittest.TestCase):
def setUp(self):
# Use a temp directory for the peers file
self.test_dir = tempfile.mkdtemp()
self.registry = PeerRegistry(data_dir=self.test_dir, config_dir=self.test_dir)
def tearDown(self):
shutil.rmtree(self.test_dir)
def test_initialization_and_empty(self):
self.assertEqual(self.registry.list_peers(), [])
def test_add_and_get_peer(self):
peer = {'peer': 'peer1', 'ip': '10.0.0.2'}
result = self.registry.add_peer(peer)
self.assertTrue(result)
self.assertEqual(self.registry.get_peer('peer1'), peer)
# Adding duplicate should fail
result = self.registry.add_peer(peer)
self.assertFalse(result)
# Defensive: check peer_obj is not None
peer_obj = self.registry.get_peer('peer1')
self.assertIsNotNone(peer_obj)
self.assertEqual(peer_obj['ip'], '10.0.0.2')
def test_remove_peer(self):
peer = {'peer': 'peer1', 'ip': '10.0.0.2'}
self.registry.add_peer(peer)
result = self.registry.remove_peer('peer1')
self.assertTrue(result)
self.assertIsNone(self.registry.get_peer('peer1'))
# Removing non-existent peer should return False
result = self.registry.remove_peer('peer1')
self.assertFalse(result)
def test_update_peer_ip(self):
peer = {'peer': 'peer1', 'ip': '10.0.0.2'}
self.registry.add_peer(peer)
result = self.registry.update_peer_ip('peer1', '10.0.0.3')
self.assertTrue(result)
peer_obj = self.registry.get_peer('peer1')
self.assertIsNotNone(peer_obj)
self.assertEqual(peer_obj['ip'], '10.0.0.3')
# Updating non-existent peer should return False
result = self.registry.update_peer_ip('peer2', '10.0.0.4')
self.assertFalse(result)
def test_persistence(self):
peer = {'peer': 'peer1', 'ip': '10.0.0.2'}
self.registry.add_peer(peer)
# Create a new registry instance to test loading from file
new_registry = PeerRegistry(data_dir=self.test_dir, config_dir=self.test_dir)
peer_obj = new_registry.get_peer('peer1')
self.assertIsNotNone(peer_obj)
self.assertEqual(peer_obj['ip'], '10.0.0.2')
def test_corrupt_file_handling(self):
# Write corrupt JSON to the peers file
peers_file = os.path.join(self.test_dir, 'peers.json')
with open(peers_file, 'w') as f:
f.write('{bad json')
# Should not raise, should load as empty
registry = PeerRegistry(data_dir=self.test_dir, config_dir=self.test_dir)
self.assertEqual(registry.list_peers(), [])
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -0,0 +1 @@
# ... moved and adapted code from test_phase4_endpoints.py ...
+149
View File
@@ -0,0 +1,149 @@
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 unittest
import tempfile
import shutil
import os
from unittest.mock import patch, MagicMock
from routing_manager import RoutingManager
import json
class TestRoutingManager(unittest.TestCase):
def setUp(self):
self.test_dir = tempfile.mkdtemp()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
os.makedirs(self.data_dir, exist_ok=True)
os.makedirs(self.config_dir, exist_ok=True)
self.manager = RoutingManager(data_dir=self.data_dir, config_dir=self.config_dir)
def tearDown(self):
shutil.rmtree(self.test_dir)
def test_initialization(self):
# Test RoutingManager initialization and config creation
self.assertTrue(os.path.exists(self.manager.routing_dir))
self.assertTrue(os.path.exists(self.manager.rules_file))
# Check that rules file contains default structure
with open(self.manager.rules_file) as f:
rules = json.load(f)
self.assertIn('nat_rules', rules)
self.assertIn('peer_routes', rules)
self.assertIn('exit_nodes', rules)
self.assertIn('bridge_routes', rules)
self.assertIn('split_routes', rules)
self.assertIn('firewall_rules', rules)
self.assertIsInstance(rules['nat_rules'], list)
self.assertIsInstance(rules['peer_routes'], dict)
self.assertIsInstance(rules['exit_nodes'], list)
self.assertIsInstance(rules['bridge_routes'], list)
self.assertIsInstance(rules['split_routes'], list)
self.assertIsInstance(rules['firewall_rules'], list)
@patch.object(RoutingManager, '_apply_nat_rule', return_value=True)
@patch.object(RoutingManager, '_remove_nat_rule', return_value=True)
def test_add_and_remove_nat_rule(self, mock_remove_nat, mock_apply_nat):
# Add a valid NAT rule
result = self.manager.add_nat_rule('10.0.0.0/24', 'eth0')
self.assertTrue(result)
# Check that the rule is persisted
with open(self.manager.rules_file) as f:
rules = json.load(f)
self.assertEqual(len(rules['nat_rules']), 1)
rule = rules['nat_rules'][0]
self.assertEqual(rule['source_network'], '10.0.0.0/24')
self.assertEqual(rule['target_interface'], 'eth0')
self.assertEqual(rule['nat_type'], 'MASQUERADE')
self.assertTrue(rule['enabled'])
# Remove the NAT rule
rule_id = rule['id']
result = self.manager.remove_nat_rule(rule_id)
self.assertTrue(result)
with open(self.manager.rules_file) as f:
rules = json.load(f)
self.assertEqual(len(rules['nat_rules']), 0)
# Test invalid NAT rule (bad CIDR)
result = self.manager.add_nat_rule('bad-cidr', 'eth0')
self.assertFalse(result)
# Test invalid NAT rule (bad interface)
result = self.manager.add_nat_rule('10.0.0.0/24', '')
self.assertFalse(result)
# Test invalid NAT rule (bad nat_type)
result = self.manager.add_nat_rule('10.0.0.0/24', 'eth0', nat_type='INVALID')
self.assertFalse(result)
# Test invalid NAT rule (bad protocol)
result = self.manager.add_nat_rule('10.0.0.0/24', 'eth0', protocol='INVALID')
self.assertFalse(result)
@patch.object(RoutingManager, '_apply_peer_route', return_value=True)
@patch.object(RoutingManager, '_remove_peer_route', return_value=True)
def test_add_and_remove_peer_route(self, mock_remove_peer, mock_apply_peer):
# Add a valid peer route
allowed_networks = ['10.0.0.0/24']
result = self.manager.add_peer_route('peer1', '10.0.0.2', allowed_networks)
self.assertTrue(result)
# Check that the route is persisted
with open(self.manager.rules_file) as f:
rules = json.load(f)
self.assertIn('peer1', rules['peer_routes'])
route = rules['peer_routes']['peer1']
self.assertEqual(route['peer_name'], 'peer1')
self.assertEqual(route['peer_ip'], '10.0.0.2')
self.assertEqual(route['allowed_networks'], allowed_networks)
self.assertEqual(route['route_type'], 'lan')
self.assertTrue(route['enabled'])
# Remove the peer route
result = self.manager.remove_peer_route('peer1')
self.assertTrue(result)
with open(self.manager.rules_file) as f:
rules = json.load(f)
self.assertNotIn('peer1', rules['peer_routes'])
# Test invalid peer route (bad peer_name)
result = self.manager.add_peer_route('', '10.0.0.2', allowed_networks)
self.assertFalse(result)
# Test invalid peer route (bad peer_ip)
result = self.manager.add_peer_route('peer2', '', allowed_networks)
self.assertFalse(result)
# Test invalid peer route (bad allowed_networks)
result = self.manager.add_peer_route('peer3', '10.0.0.3', ['bad-cidr'])
self.assertFalse(result)
# Test invalid peer route (bad route_type)
result = self.manager.add_peer_route('peer4', '10.0.0.4', allowed_networks, route_type='invalid')
self.assertFalse(result)
def test_add_exit_node(self):
pass # Test adding exit node configuration
def test_add_bridge_route(self):
pass # Test adding bridge route between peers
def test_add_split_route(self):
pass # Test adding split routing rule
def test_add_firewall_rule(self):
pass # Test adding firewall rule
def test_get_routing_status(self):
pass # Test routing status and monitoring
def test_test_routing_connectivity(self):
pass # Test routing connectivity
def test_get_routing_logs(self):
pass # Test log collection
def test_error_handling(self):
pass # Test error handling and edge cases
def test_subprocess_command_execution(self):
pass # Test subprocess command execution (mocked)
def test_route_parsing_and_analysis(self):
pass # Test route parsing and analysis
if __name__ == '__main__':
unittest.main()
+218
View File
@@ -0,0 +1,218 @@
#!/usr/bin/env python3
"""
Tests for ServiceBus
"""
import unittest
import json
import tempfile
import os
import shutil
import time
from unittest.mock import Mock, patch, MagicMock
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))
from service_bus import ServiceBus, EventType, Event
class TestServiceBus(unittest.TestCase):
"""Test the service bus functionality"""
def setUp(self):
self.service_bus = ServiceBus()
def test_initialization(self):
"""Test service bus initialization"""
self.assertIsNotNone(self.service_bus)
self.assertEqual(len(self.service_bus.service_registry), 0)
self.assertFalse(self.service_bus.running)
def test_start_stop(self):
"""Test start and stop functionality"""
self.service_bus.start()
self.assertTrue(self.service_bus.running)
self.service_bus.stop()
self.assertFalse(self.service_bus.running)
def test_register_unregister_service(self):
"""Test service registration and unregistration"""
mock_service = Mock()
mock_service.name = 'test_service'
# Register service
self.service_bus.register_service('test_service', mock_service)
self.assertIn('test_service', self.service_bus.service_registry)
self.assertEqual(self.service_bus.service_registry['test_service'], mock_service)
# List services
services = self.service_bus.list_services()
self.assertIn('test_service', services)
# Get service
service = self.service_bus.get_service('test_service')
self.assertEqual(service, mock_service)
# Unregister service
self.service_bus.unregister_service('test_service')
def test_publish_subscribe_events(self):
"""Test event publishing and subscription"""
events_received = []
def event_handler(event):
events_received.append(event)
# Subscribe to events
self.service_bus.subscribe_to_event(EventType.SERVICE_STARTED, event_handler)
# Publish event
test_data = {'service': 'test_service', 'timestamp': '2023-01-01T00:00:00Z'}
self.service_bus.publish_event(EventType.SERVICE_STARTED, 'test_source', test_data)
# Give some time for event processing
time.sleep(0.1)
# Check if event was received
self.assertIsInstance(events_received, list)
# Note: Event processing might be async, so we can't guarantee immediate reception
def test_call_service(self):
"""Test service method calling"""
mock_service = Mock()
mock_service.test_method.return_value = 'test_result'
self.service_bus.register_service('test_service', mock_service)
# Call service method
result = self.service_bus.call_service('test_service', 'test_method', param1='value1')
self.assertEqual(result, 'test_result')
mock_service.test_method.assert_called_once_with(param1='value1')
# Test calling non-existent service
with self.assertRaises(ValueError):
self.service_bus.call_service('nonexistent_service', 'test_method')
# Test calling non-existent method - Mock objects have all attributes by default
# So we need to explicitly make the attribute not exist
mock_service.nonexistent_method = None
delattr(mock_service, 'nonexistent_method')
with self.assertRaises(ValueError):
self.service_bus.call_service('test_service', 'nonexistent_method')
# Clean up
self.service_bus.unregister_service('test_service')
def test_service_orchestration(self):
"""Test service orchestration"""
mock_service = Mock()
mock_service.start = Mock()
mock_service.stop = Mock()
self.service_bus.register_service('test_service', mock_service)
# Test orchestrated start
success = self.service_bus.orchestrate_service_start('test_service')
self.assertTrue(success)
mock_service.start.assert_called_once()
# Test orchestrated stop
success = self.service_bus.orchestrate_service_stop('test_service')
self.assertTrue(success)
mock_service.stop.assert_called_once()
# Clean up
self.service_bus.unregister_service('test_service')
def test_event_history(self):
"""Test event history functionality"""
# Publish some events
for i in range(5):
self.service_bus.publish_event(
EventType.SERVICE_STARTED,
f'source_{i}',
{'index': i}
)
# Get event history
history = self.service_bus.get_event_history()
self.assertIsInstance(history, list)
# Get filtered history
filtered_history = self.service_bus.get_event_history(
event_type=EventType.SERVICE_STARTED,
limit=3
)
self.assertIsInstance(filtered_history, list)
# Clear history
self.service_bus.clear_event_history()
history = self.service_bus.get_event_history()
self.assertEqual(len(history), 0)
def test_service_dependencies(self):
"""Test service dependency management"""
# Clear any existing dependencies for email service
if 'email' in self.service_bus.service_dependencies:
self.service_bus.service_dependencies['email'] = []
# Add dependency
self.service_bus.add_service_dependency('email', 'network')
dependencies = self.service_bus.get_service_dependencies('email')
self.assertIn('network', dependencies)
# Remove dependency
self.service_bus.remove_service_dependency('email', 'network')
dependencies = self.service_bus.get_service_dependencies('email')
self.assertNotIn('network', dependencies)
def test_service_status_summary(self):
"""Test service status summary"""
mock_service = Mock()
mock_service.get_status.return_value = {'running': True, 'status': 'online'}
self.service_bus.register_service('test_service', mock_service)
summary = self.service_bus.get_service_status_summary()
self.assertIsInstance(summary, dict)
self.assertIn('total_services', summary)
def test_lifecycle_hooks(self):
"""Test lifecycle hooks"""
mock_hook = Mock()
# Add lifecycle hook
self.service_bus.add_lifecycle_hook('test_service', 'pre_start', mock_hook)
# Verify hook was added
self.assertIn('pre_start', self.service_bus.lifecycle_hooks['test_service'])
# Remove lifecycle hook
self.service_bus.remove_lifecycle_hook('test_service', 'pre_start')
# Verify hook was removed
self.assertNotIn('pre_start', self.service_bus.lifecycle_hooks['test_service'])
def test_service_restart(self):
"""Test service restart orchestration"""
mock_service = Mock()
mock_service.start = Mock()
mock_service.stop = Mock()
self.service_bus.register_service('test_service', mock_service)
# Test orchestrated restart
success = self.service_bus.orchestrate_service_restart('test_service')
self.assertTrue(success)
# Verify stop and start were called
mock_service.stop.assert_called_once()
mock_service.start.assert_called_once()
if __name__ == '__main__':
unittest.main()
+510
View File
@@ -0,0 +1,510 @@
#!/usr/bin/env python3
"""
API tests for Vault & Trust endpoints
Tests all vault-related API endpoints for secure certificate management.
"""
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 unittest
import tempfile
import shutil
import os
import json
from pathlib import Path
from unittest.mock import patch, MagicMock
# Import Flask app
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import app
class TestVaultAPI(unittest.TestCase):
"""Test cases for Vault API endpoints."""
def setUp(self):
"""Set up test environment."""
self.test_dir = tempfile.mkdtemp()
self.config_dir = os.path.join(self.test_dir, "config")
self.data_dir = os.path.join(self.test_dir, "data")
os.makedirs(self.config_dir, exist_ok=True)
os.makedirs(self.data_dir, exist_ok=True)
# Mock VaultManager
self.vault_patcher = patch('api.vault_manager')
self.mock_vault = self.vault_patcher.start()
# Create a mock vault manager instance
mock_vault_instance = MagicMock()
# Configure mock vault manager methods
mock_vault_instance.get_status.return_value = {
"ca_configured": True,
"age_configured": True,
"certificates_count": 2,
"trusted_keys_count": 3,
"trust_chains_count": 1,
"certificates": [
{
"common_name": "test.example.com",
"serial_number": 12345,
"not_valid_before": "2024-01-01T00:00:00",
"not_valid_after": "2025-01-01T00:00:00",
"cert_file": "/path/to/cert.crt",
"key_file": "/path/to/key.key",
"encrypted": True,
"expired": False
}
],
"trusted_keys": ["peer1", "peer2", "peer3"],
"ca_certificate": "base64-encoded-ca-cert",
"age_public_key": "age1testkey123456789"
}
mock_vault_instance.list_certificates.return_value = [
{
"common_name": "test.example.com",
"serial_number": 12345,
"not_valid_before": "2024-01-01T00:00:00",
"not_valid_after": "2025-01-01T00:00:00",
"cert_file": "/path/to/cert.crt",
"key_file": "/path/to/key.key",
"encrypted": True,
"expired": False
}
]
mock_vault_instance.generate_certificate.return_value = {
"common_name": "new.example.com",
"domains": ["new.example.com", "www.new.example.com"],
"cert_file": "/path/to/new.crt",
"key_file": "/path/to/new.key",
"serial_number": 67890,
"not_valid_before": "2024-01-01T00:00:00",
"not_valid_after": "2025-01-01T00:00:00",
"encrypted": True
}
mock_vault_instance.revoke_certificate.return_value = True
mock_vault_instance.get_ca_certificate.return_value = "-----BEGIN CERTIFICATE-----\nMII...\n-----END CERTIFICATE-----"
mock_vault_instance.get_age_public_key.return_value = "age1testkey123456789"
mock_vault_instance.get_trusted_keys.return_value = {
"peer1": {
"public_key": "age1peer1key",
"trust_level": "direct",
"added_at": "2024-01-01T00:00:00",
"verified": True
},
"peer2": {
"public_key": "age1peer2key",
"trust_level": "indirect",
"added_at": "2024-01-01T00:00:00",
"verified": False
}
}
mock_vault_instance.add_trusted_key.return_value = True
mock_vault_instance.remove_trusted_key.return_value = True
mock_vault_instance.verify_trust_chain.return_value = True
mock_vault_instance.get_trust_chains.return_value = {
"peer1": {
"signature": "sig123",
"data": "data123",
"verified_at": "2024-01-01T00:00:00",
"trust_level": "direct"
}
}
# Set the mock to return our configured instance
self.mock_vault.return_value = mock_vault_instance
# Inject the mock VaultManager into the Flask app
app.vault_manager = self.mock_vault.return_value
# Configure Flask app for testing
app.config['TESTING'] = True
self.client = app.test_client()
def tearDown(self):
"""Clean up test environment."""
self.vault_patcher.stop()
shutil.rmtree(self.test_dir)
def test_get_vault_status(self):
"""Test GET /api/vault/status."""
response = self.client.get('/api/vault/status')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIn("ca_configured", data)
self.assertIn("age_configured", data)
self.assertIn("certificates_count", data)
self.assertIn("trusted_keys_count", data)
self.assertIn("trust_chains_count", data)
self.assertIn("certificates", data)
self.assertIn("trusted_keys", data)
self.assertIn("ca_certificate", data)
self.assertIn("age_public_key", data)
self.assertTrue(data["ca_configured"])
self.assertTrue(data["age_configured"])
self.assertEqual(data["certificates_count"], 2)
self.assertEqual(data["trusted_keys_count"], 3)
def test_get_certificates(self):
"""Test GET /api/vault/certificates."""
response = self.client.get('/api/vault/certificates')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIsInstance(data, list)
self.assertEqual(len(data), 1)
self.assertEqual(data[0]["common_name"], "test.example.com")
self.assertTrue(data[0]["encrypted"])
self.assertFalse(data[0]["expired"])
def test_generate_certificate(self):
"""Test POST /api/vault/certificates."""
cert_data = {
"common_name": "new.example.com",
"domains": ["new.example.com", "www.new.example.com"],
"key_size": 2048,
"days": 365
}
response = self.client.post(
'/api/vault/certificates',
data=json.dumps(cert_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertEqual(data["common_name"], "new.example.com")
self.assertEqual(data["domains"], ["new.example.com", "www.new.example.com"])
self.assertTrue(data["encrypted"])
# Verify vault manager was called
self.mock_vault.return_value.generate_certificate.assert_called_once_with(
common_name="new.example.com",
domains=["new.example.com", "www.new.example.com"],
key_size=2048,
days=365
)
def test_generate_certificate_missing_common_name(self):
"""Test POST /api/vault/certificates with missing common_name."""
cert_data = {
"domains": ["test.example.com"]
}
response = self.client.post(
'/api/vault/certificates',
data=json.dumps(cert_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 500)
def test_revoke_certificate(self):
"""Test DELETE /api/vault/certificates/<common_name>."""
response = self.client.delete('/api/vault/certificates/test.example.com')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertTrue(data["revoked"])
# Verify vault manager was called
self.mock_vault.return_value.revoke_certificate.assert_called_once_with("test.example.com")
def test_get_ca_certificate(self):
"""Test GET /api/vault/ca/certificate."""
response = self.client.get('/api/vault/ca/certificate')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIn("certificate", data)
self.assertTrue(data["certificate"].startswith("-----BEGIN CERTIFICATE-----"))
def test_get_age_public_key(self):
"""Test GET /api/vault/age/public-key."""
response = self.client.get('/api/vault/age/public-key')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIn("public_key", data)
self.assertTrue(data["public_key"].startswith("age1"))
def test_get_trusted_keys(self):
"""Test GET /api/vault/trust/keys."""
response = self.client.get('/api/vault/trust/keys')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIn("peer1", data)
self.assertIn("peer2", data)
self.assertEqual(data["peer1"]["public_key"], "age1peer1key")
self.assertEqual(data["peer1"]["trust_level"], "direct")
self.assertTrue(data["peer1"]["verified"])
self.assertFalse(data["peer2"]["verified"])
def test_add_trusted_key(self):
"""Test POST /api/vault/trust/keys."""
key_data = {
"name": "new-peer",
"public_key": "age1newpeerkey",
"trust_level": "direct"
}
response = self.client.post(
'/api/vault/trust/keys',
data=json.dumps(key_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertTrue(data["added"])
# Verify vault manager was called
self.mock_vault.return_value.add_trusted_key.assert_called_once_with(
name="new-peer",
public_key="age1newpeerkey",
trust_level="direct"
)
def test_add_trusted_key_missing_name(self):
"""Test POST /api/vault/trust/keys with missing name."""
key_data = {
"public_key": "age1newpeerkey"
}
response = self.client.post(
'/api/vault/trust/keys',
data=json.dumps(key_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 500)
def test_remove_trusted_key(self):
"""Test DELETE /api/vault/trust/keys/<name>."""
response = self.client.delete('/api/vault/trust/keys/test-peer')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertTrue(data["removed"])
# Verify vault manager was called
self.mock_vault.return_value.remove_trusted_key.assert_called_once_with("test-peer")
def test_verify_trust_chain(self):
"""Test POST /api/vault/trust/verify."""
verify_data = {
"peer_name": "test-peer",
"signature": "test-signature",
"data": "test-data"
}
response = self.client.post(
'/api/vault/trust/verify',
data=json.dumps(verify_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertTrue(data["verified"])
# Verify vault manager was called
self.mock_vault.return_value.verify_trust_chain.assert_called_once_with(
peer_name="test-peer",
signature="test-signature",
data="test-data"
)
def test_verify_trust_chain_missing_data(self):
"""Test POST /api/vault/trust/verify with missing data."""
verify_data = {
"peer_name": "test-peer",
"signature": "test-signature"
}
response = self.client.post(
'/api/vault/trust/verify',
data=json.dumps(verify_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 500)
def test_get_trust_chains(self):
"""Test GET /api/vault/trust/chains."""
response = self.client.get('/api/vault/trust/chains')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertIn("peer1", data)
self.assertEqual(data["peer1"]["signature"], "sig123")
self.assertEqual(data["peer1"]["data"], "data123")
self.assertEqual(data["peer1"]["trust_level"], "direct")
def test_vault_error_handling(self):
"""Test error handling in vault endpoints."""
# Mock an exception
self.mock_vault.return_value.get_status.side_effect = Exception("Test error")
response = self.client.get('/api/vault/status')
self.assertEqual(response.status_code, 500)
data = json.loads(response.data)
self.assertIn("error", data)
def test_certificate_generation_error(self):
"""Test error handling in certificate generation."""
# Mock an exception
self.mock_vault.return_value.generate_certificate.side_effect = Exception("Generation error")
cert_data = {
"common_name": "error.example.com"
}
response = self.client.post(
'/api/vault/certificates',
data=json.dumps(cert_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 500)
data = json.loads(response.data)
self.assertIn("error", data)
def test_trust_key_operations_error(self):
"""Test error handling in trust key operations."""
# Mock an exception
self.mock_vault.return_value.add_trusted_key.side_effect = Exception("Trust error")
key_data = {
"name": "error-peer",
"public_key": "age1error"
}
response = self.client.post(
'/api/vault/trust/keys',
data=json.dumps(key_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 500)
data = json.loads(response.data)
self.assertIn("error", data)
class TestVaultAPIIntegration(unittest.TestCase):
"""Integration tests for Vault API."""
def setUp(self):
"""Set up test environment."""
self.test_dir = tempfile.mkdtemp()
self.config_dir = os.path.join(self.test_dir, "config")
self.data_dir = os.path.join(self.test_dir, "data")
os.makedirs(self.config_dir, exist_ok=True)
os.makedirs(self.data_dir, exist_ok=True)
# Configure Flask app for testing
app.config['TESTING'] = True
self.client = app.test_client()
def tearDown(self):
"""Clean up test environment."""
shutil.rmtree(self.test_dir)
def test_full_certificate_lifecycle_api(self):
"""Test complete certificate lifecycle via API."""
# Generate certificate
cert_data = {
"common_name": "api.example.com",
"domains": ["api.example.com", "www.api.example.com"],
"key_size": 2048,
"days": 365
}
response = self.client.post(
'/api/vault/certificates',
data=json.dumps(cert_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 200)
# List certificates
response = self.client.get('/api/vault/certificates')
self.assertEqual(response.status_code, 200)
# Revoke certificate
response = self.client.delete('/api/vault/certificates/api.example.com')
self.assertEqual(response.status_code, 200)
def test_full_trust_lifecycle_api(self):
"""Test complete trust lifecycle via API."""
# Add trusted key
key_data = {
"name": "api-peer",
"public_key": "age1apikey",
"trust_level": "direct"
}
response = self.client.post(
'/api/vault/trust/keys',
data=json.dumps(key_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 200)
# Verify trust chain
verify_data = {
"peer_name": "api-peer",
"signature": "api-sig",
"data": "api-data"
}
response = self.client.post(
'/api/vault/trust/verify',
data=json.dumps(verify_data),
content_type='application/json'
)
self.assertEqual(response.status_code, 200)
# Remove trusted key
response = self.client.delete('/api/vault/trust/keys/api-peer')
self.assertEqual(response.status_code, 200)
if __name__ == '__main__':
unittest.main()
+395
View File
@@ -0,0 +1,395 @@
#!/usr/bin/env python3
"""
Unit tests for VaultManager
Tests secure certificate management, trust systems, and Age encryption.
"""
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 unittest
import tempfile
import shutil
import os
import sys
from pathlib import Path
from unittest.mock import patch, MagicMock
import json
from datetime import datetime, timedelta
import subprocess
from vault_manager import VaultManager
class TestVaultManager(unittest.TestCase):
"""Test cases for VaultManager."""
def setUp(self):
"""Set up test environment."""
self.test_dir = tempfile.mkdtemp()
self.config_dir = os.path.join(self.test_dir, "config")
self.data_dir = os.path.join(self.test_dir, "data")
os.makedirs(self.config_dir, exist_ok=True)
os.makedirs(self.data_dir, exist_ok=True)
# Mock Age subprocess calls
self.age_patcher = patch('subprocess.run')
self.mock_age = self.age_patcher.start()
# Mock Age key generation output
mock_result = MagicMock()
mock_result.stdout = "age1testkey123456789\n"
mock_result.returncode = 0
self.mock_age.return_value = mock_result
self.vault = VaultManager(self.config_dir, self.data_dir)
# If Age keys were created (mocked), ensure the files exist
# (Removed Age key checks; Fernet is now used)
def tearDown(self):
"""Clean up test environment."""
self.age_patcher.stop()
shutil.rmtree(self.test_dir)
def test_init_creates_directories(self):
"""Test that initialization creates required directories."""
vault_dir = Path(self.data_dir) / "vault"
ca_dir = vault_dir / "ca"
certs_dir = vault_dir / "certs"
keys_dir = vault_dir / "keys"
trust_dir = vault_dir / "trust"
self.assertTrue(vault_dir.exists())
self.assertTrue(ca_dir.exists())
self.assertTrue(certs_dir.exists())
self.assertTrue(keys_dir.exists())
self.assertTrue(trust_dir.exists())
def test_ca_creation(self):
"""Test CA creation."""
self.assertTrue(self.vault.ca_key_file.exists())
self.assertTrue(self.vault.ca_cert_file.exists())
# Verify CA certificate properties
with open(self.vault.ca_cert_file, "rb") as f:
from cryptography import x509
cert = x509.load_pem_x509_certificate(f.read())
# Check basic constraints
basic_constraints = cert.extensions.get_extension_for_oid(
x509.oid.ExtensionOID.BASIC_CONSTRAINTS
)
self.assertTrue(basic_constraints.value.ca)
def test_generate_certificate(self):
"""Test certificate generation."""
cert_info = self.vault.generate_certificate(
common_name="test.example.com",
domains=["test.example.com", "www.test.example.com"],
key_size=2048,
days=365
)
self.assertEqual(cert_info["common_name"], "test.example.com")
self.assertEqual(cert_info["domains"], ["test.example.com", "www.test.example.com"])
self.assertTrue(cert_info["cert_file"])
self.assertTrue(cert_info["key_file"])
self.assertTrue(cert_info["encrypted"])
# Verify certificate file exists
cert_file = Path(cert_info["cert_file"])
key_file = Path(cert_info["key_file"])
self.assertTrue(cert_file.exists())
self.assertTrue(key_file.exists())
def test_generate_certificate_without_domains(self):
"""Test certificate generation without domains."""
cert_info = self.vault.generate_certificate(
common_name="simple.example.com"
)
self.assertEqual(cert_info["common_name"], "simple.example.com")
self.assertEqual(cert_info["domains"], [])
def test_list_certificates(self):
"""Test listing certificates."""
# Generate a test certificate
self.vault.generate_certificate("test.example.com")
certificates = self.vault.list_certificates()
self.assertEqual(len(certificates), 1)
self.assertEqual(certificates[0]["common_name"], "test.example.com")
self.assertFalse(certificates[0]["expired"])
def test_revoke_certificate(self):
"""Test certificate revocation."""
# Generate a test certificate
self.vault.generate_certificate("test.example.com")
# Verify certificate exists
cert_file = self.vault.certs_dir / "test.example.com.crt"
key_file = self.vault.certs_dir / "test.example.com.key"
self.assertTrue(cert_file.exists())
self.assertTrue(key_file.exists())
# Revoke certificate
result = self.vault.revoke_certificate("test.example.com")
self.assertTrue(result)
# Verify files are removed
self.assertFalse(cert_file.exists())
self.assertFalse(key_file.exists())
def test_revoke_nonexistent_certificate(self):
"""Test revoking non-existent certificate."""
result = self.vault.revoke_certificate("nonexistent.example.com")
self.assertTrue(result) # Should not raise exception
def test_add_trusted_key(self):
"""Test adding trusted key."""
result = self.vault.add_trusted_key(
name="test-peer",
public_key="age1testkey123456789",
trust_level="direct"
)
self.assertTrue(result)
# Verify key is added
trusted_keys = self.vault.get_trusted_keys()
self.assertIn("test-peer", trusted_keys)
self.assertEqual(trusted_keys["test-peer"]["public_key"], "age1testkey123456789")
self.assertEqual(trusted_keys["test-peer"]["trust_level"], "direct")
def test_remove_trusted_key(self):
"""Test removing trusted key."""
# Add a trusted key first
self.vault.add_trusted_key("test-peer", "age1testkey123456789")
# Remove the key
result = self.vault.remove_trusted_key("test-peer")
self.assertTrue(result)
# Verify key is removed
trusted_keys = self.vault.get_trusted_keys()
self.assertNotIn("test-peer", trusted_keys)
def test_remove_nonexistent_trusted_key(self):
"""Test removing non-existent trusted key."""
result = self.vault.remove_trusted_key("nonexistent-peer")
self.assertFalse(result)
def test_verify_trust_chain(self):
"""Test trust chain verification."""
# Add a trusted key first
self.vault.add_trusted_key("test-peer", "age1testkey123456789")
# Verify trust chain
result = self.vault.verify_trust_chain(
peer_name="test-peer",
signature="test-signature",
data="test-data"
)
self.assertTrue(result)
# Verify trust chain is recorded
trust_chains = self.vault.get_trust_chains()
self.assertIn("test-peer", trust_chains)
self.assertEqual(trust_chains["test-peer"]["signature"], "test-signature")
self.assertEqual(trust_chains["test-peer"]["data"], "test-data")
def test_verify_trust_chain_unknown_peer(self):
"""Test trust chain verification with unknown peer."""
result = self.vault.verify_trust_chain(
peer_name="unknown-peer",
signature="test-signature",
data="test-data"
)
self.assertFalse(result)
def test_get_ca_certificate(self):
"""Test getting CA certificate."""
cert = self.vault.get_ca_certificate()
self.assertIsInstance(cert, str)
self.assertTrue(cert.startswith("-----BEGIN CERTIFICATE-----"))
self.assertTrue(cert.endswith("-----END CERTIFICATE-----\n"))
def test_get_status(self):
"""Test getting vault status."""
status = self.vault.get_status()
self.assertIsInstance(status, dict)
self.assertIn("ca_configured", status)
self.assertIn("age_configured", status)
self.assertIn("certificates_count", status)
self.assertIn("trusted_keys_count", status)
self.assertIn("trust_chains_count", status)
self.assertIn("certificates", status)
self.assertIn("trusted_keys", status)
self.assertIn("ca_certificate", status)
self.assertIn("age_public_key", status)
self.assertTrue(status["ca_configured"])
self.assertIsInstance(status["certificates"], list)
self.assertIsInstance(status["trusted_keys"], list)
# Remove test_encrypt_file_with_age, test_decrypt_file_with_age, and any other Age-related tests
def test_certificate_with_sans(self):
"""Test certificate generation with Subject Alternative Names."""
cert_info = self.vault.generate_certificate(
common_name="sans.example.com",
domains=["sans.example.com", "www.sans.example.com", "api.sans.example.com"]
)
self.assertEqual(len(cert_info["domains"]), 3)
self.assertIn("sans.example.com", cert_info["domains"])
self.assertIn("www.sans.example.com", cert_info["domains"])
self.assertIn("api.sans.example.com", cert_info["domains"])
def test_multiple_certificates(self):
"""Test managing multiple certificates."""
# Generate multiple certificates
cert1 = self.vault.generate_certificate("cert1.example.com")
cert2 = self.vault.generate_certificate("cert2.example.com")
cert3 = self.vault.generate_certificate("cert3.example.com")
# List all certificates
certificates = self.vault.list_certificates()
self.assertEqual(len(certificates), 3)
# Verify all certificates are listed
common_names = [cert["common_name"] for cert in certificates]
self.assertIn("cert1.example.com", common_names)
self.assertIn("cert2.example.com", common_names)
self.assertIn("cert3.example.com", common_names)
def test_trust_levels(self):
"""Test different trust levels."""
# Add keys with different trust levels
self.vault.add_trusted_key("direct-peer", "age1direct", "direct")
self.vault.add_trusted_key("indirect-peer", "age1indirect", "indirect")
self.vault.add_trusted_key("verified-peer", "age1verified", "verified")
trusted_keys = self.vault.get_trusted_keys()
self.assertEqual(trusted_keys["direct-peer"]["trust_level"], "direct")
self.assertEqual(trusted_keys["indirect-peer"]["trust_level"], "indirect")
self.assertEqual(trusted_keys["verified-peer"]["trust_level"], "verified")
def test_trust_chains_persistence(self):
"""Test that trust chains are persisted."""
# Add a trusted key
self.vault.add_trusted_key("test-peer", "age1testkey")
# Verify trust chain
self.vault.verify_trust_chain("test-peer", "sig1", "data1")
self.vault.verify_trust_chain("test-peer", "sig2", "data2")
# Create new vault instance (should load from disk)
new_vault = VaultManager(self.config_dir, self.data_dir)
# Verify trust chains are loaded
trust_chains = new_vault.get_trust_chains()
self.assertIn("test-peer", trust_chains)
self.assertEqual(trust_chains["test-peer"]["signature"], "sig2")
self.assertEqual(trust_chains["test-peer"]["data"], "data2")
def test_secrets_management(self):
# Store secret
self.assertTrue(self.vault.store_secret('API_KEY', 'supersecret'))
# Get secret
self.assertEqual(self.vault.get_secret('API_KEY'), 'supersecret')
# List secrets
self.assertIn('API_KEY', self.vault.list_secrets())
# Delete secret
self.assertTrue(self.vault.delete_secret('API_KEY'))
# Secret should be gone
self.assertIsNone(self.vault.get_secret('API_KEY'))
self.assertNotIn('API_KEY', self.vault.list_secrets())
class TestVaultManagerIntegration(unittest.TestCase):
"""Integration tests for VaultManager."""
def setUp(self):
"""Set up test environment."""
self.test_dir = tempfile.mkdtemp()
self.config_dir = os.path.join(self.test_dir, "config")
self.data_dir = os.path.join(self.test_dir, "data")
os.makedirs(self.config_dir, exist_ok=True)
os.makedirs(self.data_dir, exist_ok=True)
# Mock Age subprocess calls
self.age_patcher = patch('subprocess.run')
self.mock_age = self.age_patcher.start()
# Mock Age key generation output
mock_result = MagicMock()
mock_result.stdout = "age1testkey123456789\n"
self.mock_age.return_value = mock_result
def tearDown(self):
"""Clean up test environment."""
self.age_patcher.stop()
shutil.rmtree(self.test_dir)
def test_full_certificate_lifecycle(self):
"""Test complete certificate lifecycle."""
vault = VaultManager(self.config_dir, self.data_dir)
# Generate certificate
cert_info = vault.generate_certificate("lifecycle.example.com")
self.assertTrue(cert_info["cert_file"])
self.assertTrue(cert_info["key_file"])
# List certificates
certificates = vault.list_certificates()
self.assertEqual(len(certificates), 1)
self.assertEqual(certificates[0]["common_name"], "lifecycle.example.com")
# Revoke certificate
result = vault.revoke_certificate("lifecycle.example.com")
self.assertTrue(result)
# Verify certificate is removed
certificates = vault.list_certificates()
self.assertEqual(len(certificates), 0)
def test_full_trust_lifecycle(self):
"""Test complete trust lifecycle."""
vault = VaultManager(self.config_dir, self.data_dir)
# Add trusted key
result = vault.add_trusted_key("trust-peer", "age1trustkey")
self.assertTrue(result)
# Verify trust chain
result = vault.verify_trust_chain("trust-peer", "trust-sig", "trust-data")
self.assertTrue(result)
# Remove trusted key
result = vault.remove_trusted_key("trust-peer")
self.assertTrue(result)
# Verify trust chain verification fails
result = vault.verify_trust_chain("trust-peer", "trust-sig", "trust-data")
self.assertFalse(result)
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -0,0 +1 @@
# ... moved and adapted code from test_phase2_endpoints.py ...
+328
View File
@@ -0,0 +1,328 @@
#!/usr/bin/env python3
"""
Unit tests for WireGuardManager class
"""
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 unittest
import tempfile
import os
import json
import shutil
import base64
from unittest.mock import patch, MagicMock
from datetime import datetime
# Add parent directory to path for imports
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from wireguard_manager import WireGuardManager
class TestWireGuardManager(unittest.TestCase):
"""Test cases for WireGuardManager class"""
def setUp(self):
"""Set up test environment"""
self.test_dir = tempfile.mkdtemp()
self.data_dir = os.path.join(self.test_dir, 'data')
self.config_dir = os.path.join(self.test_dir, 'config')
os.makedirs(self.data_dir, exist_ok=True)
os.makedirs(self.config_dir, exist_ok=True)
# Create WireGuardManager instance
self.wg_manager = WireGuardManager(self.data_dir, self.config_dir)
def tearDown(self):
"""Clean up test environment"""
shutil.rmtree(self.test_dir)
def test_initialization(self):
"""Test WireGuardManager initialization"""
self.assertEqual(self.wg_manager.data_dir, self.data_dir)
self.assertEqual(self.wg_manager.config_dir, self.config_dir)
self.assertTrue(os.path.exists(self.wg_manager.wireguard_dir))
self.assertTrue(os.path.exists(self.wg_manager.keys_dir))
def test_key_generation(self):
"""Test WireGuard key generation"""
# Check if keys were generated
private_key_file = os.path.join(self.wg_manager.keys_dir, 'private.key')
public_key_file = os.path.join(self.wg_manager.keys_dir, 'public.key')
self.assertTrue(os.path.exists(private_key_file))
self.assertTrue(os.path.exists(public_key_file))
# Check key content
with open(private_key_file, 'rb') as f:
private_key = f.read()
self.assertIsInstance(private_key, bytes)
self.assertGreater(len(private_key), 0)
with open(public_key_file, 'rb') as f:
public_key = f.read()
self.assertIsInstance(public_key, bytes)
self.assertGreater(len(public_key), 0)
def test_get_keys(self):
"""Test getting WireGuard keys"""
keys = self.wg_manager.get_keys()
self.assertIn('private_key', keys)
self.assertIn('public_key', keys)
self.assertIsInstance(keys['private_key'], str)
self.assertIsInstance(keys['public_key'], str)
self.assertGreater(len(keys['private_key']), 0)
self.assertGreater(len(keys['public_key']), 0)
def test_generate_peer_keys(self):
"""Test generating keys for a peer"""
peer_keys = self.wg_manager.generate_peer_keys('testpeer')
self.assertIn('private_key', peer_keys)
self.assertIn('public_key', peer_keys)
self.assertIsInstance(peer_keys['private_key'], str)
self.assertIsInstance(peer_keys['public_key'], str)
# Check if peer keys were saved
peer_keys_dir = os.path.join(self.wg_manager.keys_dir, 'peers')
peer_private_file = os.path.join(peer_keys_dir, 'testpeer_private.key')
peer_public_file = os.path.join(peer_keys_dir, 'testpeer_public.key')
self.assertTrue(os.path.exists(peer_private_file))
self.assertTrue(os.path.exists(peer_public_file))
def test_generate_config(self):
"""Test WireGuard configuration generation"""
config = self.wg_manager.generate_config('wg0', 51820)
self.assertIsInstance(config, str)
self.assertIn('[Interface]', config)
self.assertIn('PrivateKey', config)
self.assertIn('Address = 172.20.0.1/16', config)
self.assertIn('ListenPort = 51820', config)
self.assertIn('PostUp', config)
self.assertIn('PostDown', config)
def test_add_peer(self):
"""Test adding a peer to WireGuard configuration"""
# Generate peer keys first
peer_keys = self.wg_manager.generate_peer_keys('testpeer')
success = self.wg_manager.add_peer(
'testpeer',
peer_keys['public_key'],
'192.168.1.100',
'172.20.0.0/16',
25
)
self.assertTrue(success)
# Check if config file was created
config_file = os.path.join(self.wg_manager.wireguard_dir, 'wg0.conf')
self.assertTrue(os.path.exists(config_file))
# Check config content
with open(config_file, 'r') as f:
config = f.read()
self.assertIn('[Peer]', config)
self.assertIn(peer_keys['public_key'], config)
self.assertIn('AllowedIPs = 172.20.0.0/16', config)
self.assertIn('PersistentKeepalive = 25', config)
def test_remove_peer(self):
"""Test removing a peer from WireGuard configuration"""
# Add a peer first
peer_keys = self.wg_manager.generate_peer_keys('testpeer')
self.wg_manager.add_peer('testpeer', peer_keys['public_key'], '192.168.1.100')
# Remove the peer
success = self.wg_manager.remove_peer(peer_keys['public_key'])
self.assertTrue(success)
# Check if peer was removed
config_file = os.path.join(self.wg_manager.wireguard_dir, 'wg0.conf')
with open(config_file, 'r') as f:
config = f.read()
self.assertNotIn(peer_keys['public_key'], config)
def test_get_peers(self):
"""Test getting list of configured peers"""
# Add a peer first
peer_keys = self.wg_manager.generate_peer_keys('testpeer')
self.wg_manager.add_peer('testpeer', peer_keys['public_key'], '192.168.1.100')
peers = self.wg_manager.get_peers()
self.assertIsInstance(peers, list)
self.assertEqual(len(peers), 1)
self.assertIn('public_key', peers[0])
self.assertIn('allowed_ips', peers[0])
self.assertIn('persistent_keepalive', peers[0])
self.assertEqual(peers[0]['public_key'], peer_keys['public_key'])
@patch('subprocess.run')
def test_get_status(self, mock_run):
"""Test getting WireGuard status"""
# Mock WireGuard service running
mock_run.return_value.stdout = 'cell-wireguard\n'
mock_run.return_value.returncode = 0
status = self.wg_manager.get_status()
self.assertTrue(status['running'])
self.assertIn('interface', status)
self.assertIn('ip_info', status)
@patch('subprocess.run')
def test_get_status_not_running(self, mock_run):
"""Test getting WireGuard status when service is not running"""
# Mock WireGuard service not running
mock_run.return_value.stdout = ''
mock_run.return_value.returncode = 0
status = self.wg_manager.get_status()
self.assertFalse(status['running'])
@patch('subprocess.run')
def test_test_connectivity(self, mock_run):
"""Test connectivity testing"""
# Mock successful ping
mock_run.return_value.returncode = 0
mock_run.return_value.stdout = 'PING 192.168.1.100'
mock_run.return_value.stderr = ''
result = self.wg_manager.test_connectivity('192.168.1.100')
self.assertEqual(result['peer_ip'], '192.168.1.100')
self.assertTrue(result['ping_success'])
self.assertIn('192.168.1.100', result['ping_output'])
@patch('subprocess.run')
def test_test_connectivity_failure(self, mock_run):
"""Test connectivity testing with failure"""
# Mock failed ping
mock_run.return_value.returncode = 1
mock_run.return_value.stdout = ''
mock_run.return_value.stderr = 'No route to host'
result = self.wg_manager.test_connectivity('192.168.1.100')
self.assertEqual(result['peer_ip'], '192.168.1.100')
self.assertFalse(result['ping_success'])
self.assertIn('No route to host', result['ping_error'])
def test_update_peer_ip(self):
"""Test updating peer IP address"""
# Add a peer first
peer_keys = self.wg_manager.generate_peer_keys('testpeer')
self.wg_manager.add_peer('testpeer', peer_keys['public_key'], '192.168.1.100')
# Update peer IP
success = self.wg_manager.update_peer_ip(peer_keys['public_key'], '192.168.1.200')
self.assertTrue(success)
# Check if IP was updated in config
config_file = os.path.join(self.wg_manager.wireguard_dir, 'wg0.conf')
with open(config_file, 'r') as f:
config = f.read()
self.assertIn('192.168.1.200', config)
def test_get_peer_config(self):
"""Test generating peer configuration"""
peer_keys = self.wg_manager.generate_peer_keys('testpeer')
keys = self.wg_manager.get_keys()
config = self.wg_manager.get_peer_config('testpeer', '192.168.1.100', peer_keys['private_key'])
self.assertIsInstance(config, str)
self.assertIn('[Interface]', config)
self.assertIn('[Peer]', config)
self.assertIn('PrivateKey', config)
self.assertIn('Address = 192.168.1.100/32', config)
self.assertIn('DNS = 172.20.0.2', config)
self.assertIn(keys['public_key'], config)
self.assertIn('AllowedIPs = 172.20.0.0/16', config)
def test_multiple_peers(self):
"""Test managing multiple peers"""
# Add first peer
peer1_keys = self.wg_manager.generate_peer_keys('peer1')
success1 = self.wg_manager.add_peer('peer1', peer1_keys['public_key'], '192.168.1.100')
self.assertTrue(success1)
# Add second peer
peer2_keys = self.wg_manager.generate_peer_keys('peer2')
success2 = self.wg_manager.add_peer('peer2', peer2_keys['public_key'], '192.168.1.101')
self.assertTrue(success2)
# Get peers
peers = self.wg_manager.get_peers()
self.assertEqual(len(peers), 2)
# Remove first peer
success3 = self.wg_manager.remove_peer(peer1_keys['public_key'])
self.assertTrue(success3)
# Check remaining peers
peers = self.wg_manager.get_peers()
self.assertEqual(len(peers), 1)
self.assertEqual(peers[0]['public_key'], peer2_keys['public_key'])
def test_config_file_parsing(self):
"""Test parsing WireGuard configuration file"""
# Create a test config file
config_file = os.path.join(self.wg_manager.wireguard_dir, 'wg0.conf')
test_config = """[Interface]
PrivateKey = test_private_key
Address = 172.20.0.1/16
ListenPort = 51820
[Peer]
PublicKey = peer1_public_key
AllowedIPs = 172.20.0.0/16
PersistentKeepalive = 25
[Peer]
PublicKey = peer2_public_key
AllowedIPs = 172.20.1.0/24
PersistentKeepalive = 30
"""
with open(config_file, 'w') as f:
f.write(test_config)
peers = self.wg_manager.get_peers()
self.assertEqual(len(peers), 2)
self.assertEqual(peers[0]['public_key'], 'peer1_public_key')
self.assertEqual(peers[0]['allowed_ips'], '172.20.0.0/16')
self.assertEqual(peers[0]['persistent_keepalive'], 25)
self.assertEqual(peers[1]['public_key'], 'peer2_public_key')
self.assertEqual(peers[1]['allowed_ips'], '172.20.1.0/24')
self.assertEqual(peers[1]['persistent_keepalive'], 30)
def test_error_handling(self):
"""Test error handling in WireGuard operations"""
# Test with invalid public key
success = self.wg_manager.add_peer('testpeer', 'invalid_key', '192.168.1.100')
# Should still return True as it writes to config file
self.assertTrue(success)
# Test removing non-existent peer
success = self.wg_manager.remove_peer('non_existent_key')
self.assertTrue(success)
# Test updating non-existent peer IP
success = self.wg_manager.update_peer_ip('non_existent_key', '192.168.1.200')
self.assertFalse(success)
if __name__ == '__main__':
unittest.main()