#!/usr/bin/env python3 """ Unit tests for network/DNS Flask endpoints in api/app.py. Covers: GET /api/dns/records POST /api/dns/records DELETE /api/dns/records GET /api/dns/status GET /api/dns/overview GET /api/network/info POST /api/network/test """ import sys import json import unittest from pathlib import Path from unittest.mock import patch, MagicMock api_dir = Path(__file__).parent.parent / 'api' sys.path.insert(0, str(api_dir)) from app import app class TestGetDnsRecords(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.network_manager') def test_get_dns_records_returns_200_with_list(self, mock_nm): mock_nm.get_dns_records.return_value = [ {'name': 'myhost.cell', 'type': 'A', 'value': '192.168.1.10'}, {'name': 'nas.cell', 'type': 'A', 'value': '192.168.1.20'}, ] r = self.client.get('/api/dns/records') self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertIsInstance(data, list) self.assertEqual(len(data), 2) @patch('app.network_manager') def test_get_dns_records_returns_empty_list_when_none(self, mock_nm): mock_nm.get_dns_records.return_value = [] r = self.client.get('/api/dns/records') self.assertEqual(r.status_code, 200) self.assertEqual(json.loads(r.data), []) @patch('app.network_manager') def test_get_dns_records_returns_500_on_exception(self, mock_nm): mock_nm.get_dns_records.side_effect = Exception('CoreDNS unreachable') r = self.client.get('/api/dns/records') self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) class TestAddDnsRecord(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.network_manager') def test_add_dns_record_returns_200_on_valid_body(self, mock_nm): mock_nm.add_dns_record.return_value = {'success': True} r = self.client.post( '/api/dns/records', data=json.dumps({'name': 'printer.cell', 'type': 'A', 'value': '192.168.1.50'}), content_type='application/json', ) self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertIn('success', data) @patch('app.network_manager') def test_add_dns_record_returns_400_when_no_body(self, mock_nm): r = self.client.post('/api/dns/records') self.assertEqual(r.status_code, 400) self.assertIn('error', json.loads(r.data)) mock_nm.add_dns_record.assert_not_called() @patch('app.network_manager') def test_add_dns_record_returns_500_on_exception(self, mock_nm): mock_nm.add_dns_record.side_effect = Exception('Corefile write failed') r = self.client.post( '/api/dns/records', data=json.dumps({'name': 'bad.cell', 'type': 'A', 'value': '10.0.0.1'}), content_type='application/json', ) self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) class TestDeleteDnsRecord(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.network_manager') def test_delete_dns_record_returns_200_on_success(self, mock_nm): mock_nm.remove_dns_record.return_value = {'success': True} r = self.client.delete( '/api/dns/records', data=json.dumps({'name': 'printer.cell'}), content_type='application/json', ) self.assertEqual(r.status_code, 200) @patch('app.network_manager') def test_delete_dns_record_returns_500_on_exception(self, mock_nm): mock_nm.remove_dns_record.side_effect = Exception('record not found') r = self.client.delete( '/api/dns/records', data=json.dumps({'name': 'missing.cell'}), content_type='application/json', ) self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) class TestGetDnsStatus(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.network_manager') def test_get_dns_status_returns_200_with_status_dict(self, mock_nm): mock_nm.get_dns_status.return_value = { 'running': True, 'records_count': 5, 'upstreams': ['1.1.1.1', '8.8.8.8'], } r = self.client.get('/api/dns/status') self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertIn('running', data) @patch('app.network_manager') def test_get_dns_status_returns_500_on_exception(self, mock_nm): mock_nm.get_dns_status.side_effect = Exception('CoreDNS not running') r = self.client.get('/api/dns/status') self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) class TestGetDnsOverview(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.ddns_manager') @patch('app.config_manager') @patch('app.network_manager') def test_get_dns_overview_returns_200(self, mock_nm, mock_cm, mock_dm): mock_nm.get_dns_overview.return_value = { 'mode': 'pic_ngo', 'provider': 'pic_ngo', 'effective_domain': 'mycell.pic.ngo', 'internal_domain': 'cell', 'public_ip': '1.2.3.4', 'public_records': [], 'internal_records': [], 'service_subdomains': [], 'registration_status': {'registered': True}, } r = self.client.get('/api/dns/overview') self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertEqual(data['mode'], 'pic_ngo') self.assertEqual(data['effective_domain'], 'mycell.pic.ngo') mock_nm.get_dns_overview.assert_called_once_with(mock_cm, mock_dm) @patch('app.ddns_manager') @patch('app.config_manager') @patch('app.network_manager') def test_get_dns_overview_returns_500_on_exception(self, mock_nm, mock_cm, mock_dm): mock_nm.get_dns_overview.side_effect = Exception('boom') r = self.client.get('/api/dns/overview') self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) class TestGetNetworkInfo(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.network_manager') def test_get_network_info_returns_200_with_info_dict(self, mock_nm): mock_nm.get_network_info.return_value = { 'interfaces': ['eth0', 'wg0'], 'gateway': '192.168.1.1', 'dns': ['127.0.0.1'], } r = self.client.get('/api/network/info') self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertIn('interfaces', data) @patch('app.network_manager') def test_get_network_info_returns_500_on_exception(self, mock_nm): mock_nm.get_network_info.side_effect = Exception('network unreachable') r = self.client.get('/api/network/info') self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) class TestNetworkTest(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() @patch('app.network_manager') def test_network_test_returns_200_with_result(self, mock_nm): mock_nm.test_connectivity.return_value = { 'internet': True, 'dns': True, 'latency_ms': 15, } r = self.client.post('/api/network/test') self.assertEqual(r.status_code, 200) data = json.loads(r.data) self.assertIn('internet', data) @patch('app.network_manager') def test_network_test_returns_500_on_exception(self, mock_nm): mock_nm.test_connectivity.side_effect = Exception('ping failed') r = self.client.post('/api/network/test') self.assertEqual(r.status_code, 500) self.assertIn('error', json.loads(r.data)) if __name__ == '__main__': unittest.main()