#!/usr/bin/env python3 """Unit tests for CellLinkManager (cell-to-cell VPN connections).""" import sys from pathlib import 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 MagicMock, patch from cell_link_manager import CellLinkManager def _make_wg_mock(): wg = MagicMock() wg.get_keys.return_value = {'public_key': 'serverpubkey=', 'private_key': 'serverprivkey='} wg.get_server_config.return_value = { 'endpoint': '1.2.3.4:51820', 'port': 51820, 'dns_ip': '10.0.0.3', 'split_tunnel_ips': '10.0.0.0/24, 172.20.0.0/16', } wg._get_configured_network.return_value = '10.0.0.0/24' wg._get_configured_address.return_value = '10.0.0.1/24' wg.add_cell_peer.return_value = True wg.remove_peer.return_value = True return wg def _make_nm_mock(): nm = MagicMock() nm.add_cell_dns_forward.return_value = {'restarted': ['cell-dns (reloaded)'], 'warnings': []} nm.remove_cell_dns_forward.return_value = {'restarted': ['cell-dns (reloaded)'], 'warnings': []} return nm SAMPLE_INVITE = { 'cell_name': 'office', 'public_key': 'officepubkey=', 'endpoint': '5.6.7.8:51820', 'vpn_subnet': '10.1.0.0/24', 'dns_ip': '10.1.0.1', 'domain': 'office.cell', 'version': 1, } class TestCellLinkManagerInvite(unittest.TestCase): def setUp(self): self.test_dir = tempfile.mkdtemp() self.wg = _make_wg_mock() self.nm = _make_nm_mock() self.mgr = CellLinkManager(self.test_dir, self.test_dir, self.wg, self.nm) def tearDown(self): shutil.rmtree(self.test_dir) def test_generate_invite_has_required_fields(self): invite = self.mgr.generate_invite('mycell', 'home.cell') for field in ('cell_name', 'public_key', 'endpoint', 'vpn_subnet', 'dns_ip', 'domain', 'version'): self.assertIn(field, invite, f"Missing field: {field}") def test_generate_invite_uses_wg_public_key(self): invite = self.mgr.generate_invite('mycell', 'home.cell') self.assertEqual(invite['public_key'], 'serverpubkey=') def test_generate_invite_uses_configured_network(self): invite = self.mgr.generate_invite('mycell', 'home.cell') self.assertEqual(invite['vpn_subnet'], '10.0.0.0/24') def test_generate_invite_dns_ip_is_server_vpn_ip(self): invite = self.mgr.generate_invite('mycell', 'home.cell') self.assertEqual(invite['dns_ip'], '10.0.0.1') def test_generate_invite_uses_supplied_identity(self): invite = self.mgr.generate_invite('myhome', 'myhome.local') self.assertEqual(invite['cell_name'], 'myhome') self.assertEqual(invite['domain'], 'myhome.local') class TestCellLinkManagerConnections(unittest.TestCase): def setUp(self): self.test_dir = tempfile.mkdtemp() self.wg = _make_wg_mock() self.nm = _make_nm_mock() self.mgr = CellLinkManager(self.test_dir, self.test_dir, self.wg, self.nm) def tearDown(self): shutil.rmtree(self.test_dir) def test_add_connection_stores_link(self): self.mgr.add_connection(SAMPLE_INVITE) links = self.mgr.list_connections() self.assertEqual(len(links), 1) self.assertEqual(links[0]['cell_name'], 'office') def test_add_connection_calls_add_cell_peer(self): self.mgr.add_connection(SAMPLE_INVITE) self.wg.add_cell_peer.assert_called_once_with( name='office', public_key='officepubkey=', endpoint='5.6.7.8:51820', vpn_subnet='10.1.0.0/24', ) def test_add_connection_calls_dns_forward(self): self.mgr.add_connection(SAMPLE_INVITE) self.nm.add_cell_dns_forward.assert_called_once_with( domain='office.cell', dns_ip='10.1.0.1' ) def test_add_connection_duplicate_raises(self): self.mgr.add_connection(SAMPLE_INVITE) with self.assertRaises(ValueError): self.mgr.add_connection(SAMPLE_INVITE) def test_add_connection_persists_to_disk(self): self.mgr.add_connection(SAMPLE_INVITE) # Create a fresh manager reading same dir mgr2 = CellLinkManager(self.test_dir, self.test_dir, self.wg, self.nm) links = mgr2.list_connections() self.assertEqual(len(links), 1) self.assertEqual(links[0]['cell_name'], 'office') def test_remove_connection_calls_wg_remove_peer(self): self.mgr.add_connection(SAMPLE_INVITE) self.mgr.remove_connection('office') self.wg.remove_peer.assert_called_once_with('officepubkey=') def test_remove_connection_calls_dns_remove(self): self.mgr.add_connection(SAMPLE_INVITE) self.mgr.remove_connection('office') self.nm.remove_cell_dns_forward.assert_called_once_with('office.cell') def test_remove_connection_deletes_from_list(self): self.mgr.add_connection(SAMPLE_INVITE) self.mgr.remove_connection('office') self.assertEqual(len(self.mgr.list_connections()), 0) def test_remove_nonexistent_raises(self): with self.assertRaises(ValueError): self.mgr.remove_connection('nobody') def test_list_connections_empty_by_default(self): self.assertEqual(self.mgr.list_connections(), []) def test_multiple_connections(self): self.mgr.add_connection(SAMPLE_INVITE) second = {**SAMPLE_INVITE, 'cell_name': 'cabin', 'public_key': 'cabinpubkey=', 'vpn_subnet': '10.2.0.0/24', 'dns_ip': '10.2.0.1', 'domain': 'cabin.cell'} self.mgr.add_connection(second) self.assertEqual(len(self.mgr.list_connections()), 2) if __name__ == '__main__': unittest.main()