""" Tests for PUT /api/config input validation (400 paths). These are the highest-risk untested paths: the only server-side guard against bad subnet/port values entering persistent config. """ import json import sys import os import unittest from unittest.mock import patch, MagicMock sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'api')) def _make_client(): from app import app app.config['TESTING'] = True return app.test_client() def _put(client, payload): return client.put( '/api/config', data=json.dumps(payload), content_type='application/json', ) # --------------------------------------------------------------------------- # ip_range validation # --------------------------------------------------------------------------- class TestIpRangeValidation(unittest.TestCase): def setUp(self): self.client = _make_client() def test_non_rfc1918_returns_400(self): r = _put(self.client, {'ip_range': '1.2.3.0/24'}) self.assertEqual(r.status_code, 400) body = json.loads(r.data) self.assertIn('error', body) self.assertIn('RFC-1918', body['error']) def test_172_0_subnet_returns_400(self): # 172.0.0.0/24 is NOT in 172.16.0.0/12 — was the bug on the dev machine r = _put(self.client, {'ip_range': '172.0.0.0/24'}) self.assertEqual(r.status_code, 400) def test_172_15_subnet_returns_400(self): # One prefix below the 172.16.0.0/12 boundary r = _put(self.client, {'ip_range': '172.15.0.0/24'}) self.assertEqual(r.status_code, 400) def test_172_32_subnet_returns_400(self): # One prefix above the 172.31.255.255 boundary r = _put(self.client, {'ip_range': '172.32.0.0/24'}) self.assertEqual(r.status_code, 400) def test_public_ip_returns_400(self): r = _put(self.client, {'ip_range': '8.8.0.0/16'}) self.assertEqual(r.status_code, 400) def test_172_16_exact_boundary_accepted(self): # 172.16.0.0/12 is the exact lower boundary — must be valid r = _put(self.client, {'ip_range': '172.16.0.0/12'}) # 200 or 202 — just not 400 self.assertNotEqual(r.status_code, 400) def test_10_network_accepted(self): r = _put(self.client, {'ip_range': '10.0.0.0/8'}) self.assertNotEqual(r.status_code, 400) def test_192_168_network_accepted(self): r = _put(self.client, {'ip_range': '192.168.0.0/16'}) self.assertNotEqual(r.status_code, 400) def test_invalid_cidr_syntax_returns_400(self): r = _put(self.client, {'ip_range': 'not-a-cidr'}) self.assertEqual(r.status_code, 400) # --------------------------------------------------------------------------- # Port range validation # --------------------------------------------------------------------------- class TestPortValidation(unittest.TestCase): def setUp(self): self.client = _make_client() def test_dns_port_zero_returns_400(self): r = _put(self.client, {'network': {'dns_port': 0}}) self.assertEqual(r.status_code, 400) body = json.loads(r.data) self.assertIn('dns_port', body.get('error', '')) def test_dns_port_65536_returns_400(self): r = _put(self.client, {'network': {'dns_port': 65536}}) self.assertEqual(r.status_code, 400) def test_wireguard_port_zero_returns_400(self): r = _put(self.client, {'wireguard': {'port': 0}}) self.assertEqual(r.status_code, 400) def test_wireguard_port_65536_returns_400(self): r = _put(self.client, {'wireguard': {'port': 65536}}) self.assertEqual(r.status_code, 400) def test_wireguard_port_1_accepted(self): r = _put(self.client, {'wireguard': {'port': 1}}) self.assertNotEqual(r.status_code, 400) def test_wireguard_port_65535_accepted(self): r = _put(self.client, {'wireguard': {'port': 65535}}) self.assertNotEqual(r.status_code, 400) def test_email_smtp_port_zero_returns_400(self): r = _put(self.client, {'email': {'smtp_port': 0}}) self.assertEqual(r.status_code, 400) def test_calendar_port_negative_returns_400(self): r = _put(self.client, {'calendar': {'port': -1}}) self.assertEqual(r.status_code, 400) # --------------------------------------------------------------------------- # WireGuard address validation # --------------------------------------------------------------------------- class TestWireguardAddressValidation(unittest.TestCase): def setUp(self): self.client = _make_client() def test_bad_wg_address_returns_400(self): r = _put(self.client, {'wireguard': {'address': 'not-an-ip'}}) self.assertEqual(r.status_code, 400) body = json.loads(r.data) self.assertIn('wireguard.address', body.get('error', '')) def test_ip_without_prefix_returns_400(self): r = _put(self.client, {'wireguard': {'address': '10.0.0.1'}}) self.assertEqual(r.status_code, 400) def test_valid_wg_address_accepted(self): r = _put(self.client, {'wireguard': {'address': '10.0.0.1/24'}}) self.assertNotEqual(r.status_code, 400) # --------------------------------------------------------------------------- # Body validation # --------------------------------------------------------------------------- class TestBodyValidation(unittest.TestCase): def setUp(self): self.client = _make_client() def test_no_body_returns_400(self): r = self.client.put('/api/config', content_type='application/json') self.assertEqual(r.status_code, 400) def test_empty_body_returns_400(self): r = self.client.put('/api/config', data='', content_type='application/json') self.assertEqual(r.status_code, 400) def test_valid_cell_name_change_returns_200(self): r = _put(self.client, {'cell_name': 'testcell'}) self.assertEqual(r.status_code, 200) if __name__ == '__main__': unittest.main()