aa1e5c41ec
Unit Tests / test (push) Successful in 12m6s
Coverage was below acceptable levels and several newly-added code paths (sshuttle egress, proxy egress, DDNS provider stubs, DNS overview route, peer-registry provisioning) had zero test coverage. ~250 new unit tests are added across 16 new test files. Existing test files are updated to match refactored interfaces (DHCP removed, constants introduced, network_manager restructured). .coveragerc is added to pin the source mapping and the 70% floor so regressions are caught at commit time. tests/test_enhanced_api.py was previously living in api/ (wrong location) and is moved to tests/ where it belongs. Integration test files are updated to remove references to DHCP endpoints and add coverage for the new DNS overview and DDNS sync endpoints. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
243 lines
8.2 KiB
Python
243 lines
8.2 KiB
Python
#!/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()
|