test: add .env write verification for port changes
TestEnvFileWrittenOnPortChange (7 tests) confirms that PUT /api/config with a port change actually writes the new variable to the .env file consumed by docker compose — the critical link between 'config saved' and 'docker binding changes on next restart'. Tests cover calendar, webdav, filegator, wireguard, email; also verifies changing one port does not reset unrelated ports, and WG_PORT appears exactly once with the new value. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -219,11 +219,14 @@ class TestPortChangeDetection(unittest.TestCase):
|
||||
# Remove any stored service configs so we start clean
|
||||
for key in ('calendar', 'files', 'wireguard', 'network', 'email'):
|
||||
config_manager.configs.pop(key, None)
|
||||
self.tmp = tempfile.mkdtemp()
|
||||
self.env_path = os.path.join(self.tmp, '.env')
|
||||
|
||||
def tearDown(self):
|
||||
_clear_pending_restart()
|
||||
for key in ('calendar', 'files', 'wireguard', 'network', 'email'):
|
||||
config_manager.configs.pop(key, None)
|
||||
shutil.rmtree(self.tmp)
|
||||
|
||||
def _put_config(self, payload):
|
||||
return self.client.put('/api/config',
|
||||
@@ -271,5 +274,87 @@ class TestPortChangeDetection(unittest.TestCase):
|
||||
self.assertFalse(p.get('needs_restart', False))
|
||||
|
||||
|
||||
class TestEnvFileWrittenOnPortChange(unittest.TestCase):
|
||||
"""Verify that PUT /api/config with a port change actually writes the new
|
||||
port variable to the .env file consumed by docker compose.
|
||||
|
||||
This is the critical link between 'port saved in config' and 'docker binding
|
||||
changes on next restart'. Without this the container would still bind the old
|
||||
port even after apply is clicked.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
app.config['TESTING'] = True
|
||||
self.client = app.test_client()
|
||||
_clear_pending_restart()
|
||||
for key in ('calendar', 'files', 'wireguard', 'network', 'email'):
|
||||
config_manager.configs.pop(key, None)
|
||||
self.tmp = tempfile.mkdtemp()
|
||||
self.env_path = os.path.join(self.tmp, '.env')
|
||||
# Pre-create .env so write_env_file can overwrite it
|
||||
import ip_utils
|
||||
ip_utils.write_env_file('172.20.0.0/16', self.env_path)
|
||||
|
||||
def tearDown(self):
|
||||
_clear_pending_restart()
|
||||
for key in ('calendar', 'files', 'wireguard', 'network', 'email'):
|
||||
config_manager.configs.pop(key, None)
|
||||
shutil.rmtree(self.tmp)
|
||||
|
||||
def _put_config(self, payload):
|
||||
with patch.dict(os.environ, {'COMPOSE_ENV_FILE': self.env_path}):
|
||||
return self.client.put('/api/config',
|
||||
data=json.dumps(payload),
|
||||
content_type='application/json')
|
||||
|
||||
def _env_content(self):
|
||||
return open(self.env_path).read()
|
||||
|
||||
def test_calendar_port_written_to_env(self):
|
||||
self._put_config({'calendar': {'port': 5299}})
|
||||
self.assertIn('RADICALE_PORT=5299', self._env_content())
|
||||
|
||||
def test_webdav_port_written_to_env(self):
|
||||
self._put_config({'files': {'port': 8181}})
|
||||
self.assertIn('WEBDAV_PORT=8181', self._env_content())
|
||||
|
||||
def test_filegator_port_written_to_env(self):
|
||||
self._put_config({'files': {'manager_port': 9090}})
|
||||
self.assertIn('FILEGATOR_PORT=9090', self._env_content())
|
||||
|
||||
def test_wireguard_port_written_to_env(self):
|
||||
self._put_config({'wireguard': {'port': 51999}})
|
||||
self.assertIn('WG_PORT=51999', self._env_content())
|
||||
|
||||
def test_email_smtp_port_written_to_env(self):
|
||||
self._put_config({'email': {'smtp_port': 2525}})
|
||||
self.assertIn('MAIL_SMTP_PORT=2525', self._env_content())
|
||||
|
||||
def test_other_ports_unchanged_when_one_port_changes(self):
|
||||
"""Changing calendar port must not reset unrelated ports to defaults."""
|
||||
# First set webdav to a non-default
|
||||
self._put_config({'files': {'port': 8181}})
|
||||
# Then change calendar port
|
||||
self._put_config({'calendar': {'port': 5299}})
|
||||
content = self._env_content()
|
||||
self.assertIn('RADICALE_PORT=5299', content)
|
||||
self.assertIn('WEBDAV_PORT=8181', content) # must stay at 8181, not revert to 8080
|
||||
|
||||
def test_env_uses_symmetric_wg_port_for_docker_binding(self):
|
||||
"""WG_PORT must be the same value on both sides of the docker port mapping.
|
||||
|
||||
docker-compose.yml uses ${WG_PORT:-51820}:${WG_PORT:-51820}/udp so the
|
||||
host port and container port are always the same. This test verifies
|
||||
a port change writes a single consistent value to .env so the daemon's
|
||||
ListenPort matches the Docker binding.
|
||||
"""
|
||||
self._put_config({'wireguard': {'port': 51999}})
|
||||
content = self._env_content()
|
||||
self.assertIn('WG_PORT=51999', content)
|
||||
# There must be only one WG_PORT line (no duplicate with old value)
|
||||
wg_lines = [l for l in content.splitlines() if l.startswith('WG_PORT=')]
|
||||
self.assertEqual(len(wg_lines), 1, f'Expected exactly one WG_PORT line, got: {wg_lines}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user