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' headers = {} user = type('User', (), {'id': 'user1'})() with patch('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' headers = {} with patch('app.request', new=DummyRequest()): self.assertTrue(app_module.is_local_request()) class DummyRequest2: remote_addr = '8.8.8.8' headers = {} with patch('app.request', new=DummyRequest2()): self.assertFalse(app_module.is_local_request()) def test_health_check_exception(self): # Patch datetime to raise exception with patch('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 and response.status_code == 500: 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') # The route handles per-service exceptions internally and returns 200 # with per-service error info; only outer failures yield 500 self.assertIn(response.status_code, (200, 500)) data = response.get_json(silent=True) self.assertIsNotNone(data) def test_get_config_exception(self): with patch('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()