fix: wireguard_port identity change and check_port_open verification

Bug 1 — port not propagated to wg0.conf:
  The identity update path (wireguard_port via PUT /api/config) was calling
  wireguard_manager.update_config() which only saves to a JSON file via
  BaseServiceManager. wg0.conf was never updated, so after a container
  restart the WireGuard interface would still listen on the old port.
  Fix: call apply_config() instead — it writes ListenPort into wg0.conf.

Bug 2 — check_port_open ignored configured port:
  check_port_open() checked for 'listening port' in wg show output but
  never compared it against the configured port. A port-mismatch (e.g.
  after config change but before restart) would return True — misleading.
  Fix: require 'listening port: {configured_port}' to match exactly.

Tests added:
  - test_check_port_open_wrong_port_returns_false
  - test_check_port_open_explicit_port_matches
  - test_check_port_open_explicit_port_mismatch
  - test_wireguard_port_identity_change_calls_apply_config
  - test_wireguard_port_same_value_does_not_call_apply_config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 08:41:22 -04:00
parent 9677755b4f
commit de5ff75a2e
4 changed files with 90 additions and 5 deletions
+61
View File
@@ -227,5 +227,66 @@ class TestWireGuardEndpoints(unittest.TestCase):
self.assertIn('error', json.loads(r.data))
class TestWireGuardPortPropagation(unittest.TestCase):
"""
Test that changing wireguard_port via the identity config path calls
wireguard_manager.apply_config (writes wg0.conf), not just update_config
(which only saves a JSON file and never touches wg0.conf).
"""
def setUp(self):
app.config['TESTING'] = True
self.client = app.test_client()
@patch('app._set_pending_restart')
@patch('app.wireguard_manager')
@patch('app.config_manager')
def test_wireguard_port_identity_change_calls_apply_config(
self, mock_cm, mock_wg, mock_pending
):
"""wireguard_port in identity update must call apply_config, not just update_config."""
mock_cm.configs = {
'_identity': {'wireguard_port': 51820, 'ip_range': '10.0.0.0/24'},
'wireguard': {'port': 51820},
}
mock_cm.service_schemas = {}
mock_cm.update_service_config.return_value = None
mock_cm._save_all_configs.return_value = None
mock_wg.apply_config.return_value = {'restarted': [], 'warnings': []}
mock_pending.return_value = None
r = self.client.put(
'/api/config',
data=json.dumps({'wireguard_port': 51821}),
content_type='application/json',
)
self.assertEqual(r.status_code, 200)
mock_wg.apply_config.assert_called_once_with({'port': 51821})
@patch('app._set_pending_restart')
@patch('app.wireguard_manager')
@patch('app.config_manager')
def test_wireguard_port_same_value_does_not_call_apply_config(
self, mock_cm, mock_wg, mock_pending
):
"""apply_config must NOT be called when the new port equals the current port."""
mock_cm.configs = {
'_identity': {'wireguard_port': 51820, 'ip_range': '10.0.0.0/24'},
'wireguard': {'port': 51820},
}
mock_cm.service_schemas = {}
mock_cm.update_service_config.return_value = None
mock_cm._save_all_configs.return_value = None
mock_pending.return_value = None
r = self.client.put(
'/api/config',
data=json.dumps({'wireguard_port': 51820}),
content_type='application/json',
)
self.assertEqual(r.status_code, 200)
mock_wg.apply_config.assert_not_called()
if __name__ == '__main__':
unittest.main()