#!/usr/bin/env python3 """Tests for ip_utils — IP derivation from subnet.""" import sys import os import tempfile import unittest from pathlib import Path api_dir = Path(__file__).parent.parent / 'api' sys.path.insert(0, str(api_dir)) import ip_utils class TestGetServiceIps(unittest.TestCase): def setUp(self): self.ips = ip_utils.get_service_ips('172.20.0.0/16') def test_returns_all_keys(self): for name in ip_utils.CONTAINER_OFFSETS: self.assertIn(name, self.ips) def test_default_subnet_caddy(self): self.assertEqual(self.ips['caddy'], '172.20.0.2') def test_default_subnet_dns(self): self.assertEqual(self.ips['dns'], '172.20.0.3') def test_default_subnet_api(self): self.assertEqual(self.ips['api'], '172.20.0.10') def test_default_subnet_virtual_ips(self): self.assertEqual(self.ips['vip_calendar'], '172.20.0.21') self.assertEqual(self.ips['vip_files'], '172.20.0.22') self.assertEqual(self.ips['vip_mail'], '172.20.0.23') self.assertEqual(self.ips['vip_webdav'], '172.20.0.24') def test_different_subnet_shifts_all_ips(self): ips = ip_utils.get_service_ips('10.0.0.0/24') self.assertEqual(ips['caddy'], '10.0.0.2') self.assertEqual(ips['dns'], '10.0.0.3') self.assertEqual(ips['api'], '10.0.0.10') self.assertEqual(ips['vip_calendar'], '10.0.0.21') def test_non_zero_third_octet_subnet(self): ips = ip_utils.get_service_ips('192.168.5.0/24') self.assertEqual(ips['caddy'], '192.168.5.2') self.assertEqual(ips['vip_webdav'], '192.168.5.24') def test_strict_false_accepts_host_bit_set(self): # e.g. user types "172.20.0.1/16" — should work same as "172.20.0.0/16" ips = ip_utils.get_service_ips('172.20.0.1/16') self.assertEqual(ips['caddy'], '172.20.0.2') def test_all_ips_are_strings(self): for name, ip in self.ips.items(): self.assertIsInstance(ip, str, f'{name} is not a string') def test_all_ips_unique(self): self.assertEqual(len(set(self.ips.values())), len(self.ips)) class TestGetVirtualIps(unittest.TestCase): def test_returns_four_services(self): vips = ip_utils.get_virtual_ips('172.20.0.0/16') self.assertEqual(set(vips.keys()), {'calendar', 'files', 'mail', 'webdav'}) def test_values_match_get_service_ips(self): full = ip_utils.get_service_ips('172.20.0.0/16') vips = ip_utils.get_virtual_ips('172.20.0.0/16') self.assertEqual(vips['calendar'], full['vip_calendar']) self.assertEqual(vips['files'], full['vip_files']) self.assertEqual(vips['mail'], full['vip_mail']) self.assertEqual(vips['webdav'], full['vip_webdav']) def test_different_subnet(self): vips = ip_utils.get_virtual_ips('10.10.0.0/16') self.assertEqual(vips['calendar'], '10.10.0.21') self.assertEqual(vips['webdav'], '10.10.0.24') class TestWriteEnvFile(unittest.TestCase): def setUp(self): self.tmp = tempfile.mkdtemp() self.env_path = os.path.join(self.tmp, '.env') def tearDown(self): import shutil shutil.rmtree(self.tmp) def test_creates_file(self): ip_utils.write_env_file('172.20.0.0/16', self.env_path) self.assertTrue(os.path.exists(self.env_path)) def test_returns_true_on_success(self): result = ip_utils.write_env_file('172.20.0.0/16', self.env_path) self.assertTrue(result) def test_returns_false_on_unwritable_path(self): result = ip_utils.write_env_file('172.20.0.0/16', '/nonexistent/deep/path/.env') self.assertFalse(result) def test_contains_cell_network(self): ip_utils.write_env_file('172.20.0.0/16', self.env_path) content = open(self.env_path).read() self.assertIn('CELL_NETWORK=172.20.0.0/16', content) def test_contains_caddy_ip(self): ip_utils.write_env_file('172.20.0.0/16', self.env_path) content = open(self.env_path).read() self.assertIn('CADDY_IP=172.20.0.2', content) def test_contains_all_env_vars(self): ip_utils.write_env_file('172.20.0.0/16', self.env_path) content = open(self.env_path).read() for var in ip_utils.ENV_VAR_NAMES.values(): self.assertIn(var + '=', content, f'{var} missing from .env') def test_custom_subnet_generates_correct_ips(self): ip_utils.write_env_file('10.5.0.0/24', self.env_path) content = open(self.env_path).read() self.assertIn('CELL_NETWORK=10.5.0.0/24', content) self.assertIn('CADDY_IP=10.5.0.2', content) self.assertIn('DNS_IP=10.5.0.3', content) self.assertIn('API_IP=10.5.0.10', content) self.assertNotIn('172.20', content) def test_overwrite_updates_ips(self): ip_utils.write_env_file('172.20.0.0/16', self.env_path) ip_utils.write_env_file('10.0.0.0/24', self.env_path) content = open(self.env_path).read() self.assertIn('CADDY_IP=10.0.0.2', content) self.assertNotIn('172.20', content) def test_each_line_is_key_equals_value(self): ip_utils.write_env_file('172.20.0.0/16', self.env_path) for line in open(self.env_path).read().splitlines(): if line.strip(): self.assertIn('=', line, f'Bad line format: {line!r}') key, _, val = line.partition('=') self.assertTrue(key.isupper() or '_' in key) self.assertTrue(val.strip()) if __name__ == '__main__': unittest.main()