Stop invalidating sudo timestamp on mode exit

This commit is contained in:
Philip Henning 2026-09-03 19:28:01 +02:00
parent 026bbc4eff
commit 68a9696e7d
6 changed files with 55 additions and 43 deletions

View file

@ -1,7 +1,7 @@
"""Tests for application-level authorization flow."""
from contextlib import contextmanager
from unittest.mock import Mock
from unittest.mock import Mock, patch
from textual.app import SuspendNotSupported
@ -125,3 +125,22 @@ class TestPrivilegedModeAuthorization:
app.update_status.assert_called_once_with(
"Interactive authorization requires terminal suspension; remaining in Read-only Mode."
)
@patch("src.hosts.core.manager.subprocess.run")
def test_leaving_privileged_mode_preserves_the_sudo_timestamp(self, mock_run):
"""Test Ctrl+E clears app state without invalidating sudo globally."""
app = HostsManagerApp()
app.edit_mode = True
app.manager.edit_mode = True
app.manager.permission_manager.has_sudo = True
app.manager.permission_manager._sudo_validated = True
app.update_status = Mock()
app.action_toggle_edit_mode()
assert not app.edit_mode
assert not app.manager.edit_mode
assert not app.manager.permission_manager.has_sudo
assert not app.manager.permission_manager._sudo_validated
mock_run.assert_not_called()
app.update_status.assert_called_once_with("Edit mode disabled")

View file

@ -12,6 +12,7 @@ from pathlib import Path
from unittest.mock import Mock, patch
from src.hosts.core.manager import PermissionManager, HostsManager
from src.hosts.core.models import HostEntry, HostsFile
from src.hosts.core.commands import ToggleEntryCommand
class TestPermissionManager:
@ -183,32 +184,17 @@ class TestPermissionManager:
assert not result
@patch("subprocess.run")
def test_release_sudo(self, mock_run):
"""Test releasing sudo permissions."""
def test_clear_permission_state_preserves_global_sudo_timestamp(self, mock_run):
"""Test exiting Privileged Mode preserves the global sudo timestamp."""
pm = PermissionManager()
pm.has_sudo = True
pm._sudo_validated = True
pm.release_sudo()
pm.clear_permission_state()
assert not pm.has_sudo
assert not pm._sudo_validated
mock_run.assert_called_once_with(["sudo", "-k"], capture_output=True, timeout=5)
@patch("subprocess.run")
def test_release_sudo_exception(self, mock_run):
"""Test releasing sudo with exception."""
mock_run.side_effect = Exception("Test error")
pm = PermissionManager()
pm.has_sudo = True
pm._sudo_validated = True
pm.release_sudo()
# Should still reset state even if command fails
assert not pm.has_sudo
assert not pm._sudo_validated
mock_run.assert_not_called()
class TestHostsManager:
@ -306,14 +292,25 @@ class TestHostsManager:
assert not manager.edit_mode
def test_exit_edit_mode_success(self):
"""Test exiting edit mode successfully."""
"""Test exit clears the Privileged Mode session state."""
with tempfile.NamedTemporaryFile() as temp_file:
manager = HostsManager(temp_file.name)
manager.edit_mode = True
manager._backup_path = Path("/tmp/backup")
hosts_file = HostsFile(
entries=[
HostEntry("192.0.2.1", ["example.test"]),
HostEntry("192.0.2.2", ["example-two.test"]),
]
)
manager.undo_redo_history.execute_command(ToggleEntryCommand(0), hosts_file)
manager.undo_redo_history.execute_command(ToggleEntryCommand(1), hosts_file)
manager.undo_redo_history.undo(hosts_file)
assert manager.can_undo()
assert manager.can_redo()
# Mock permission manager
manager.permission_manager.release_sudo = Mock()
manager.permission_manager.clear_permission_state = Mock()
success, message = manager.exit_edit_mode()
@ -321,7 +318,9 @@ class TestHostsManager:
assert "disabled" in message
assert not manager.edit_mode
assert manager._backup_path is None
manager.permission_manager.release_sudo.assert_called_once()
assert not manager.can_undo()
assert not manager.can_redo()
manager.permission_manager.clear_permission_state.assert_called_once()
def test_exit_edit_mode_not_in_edit(self):
"""Test exiting edit mode when not in edit mode."""
@ -341,7 +340,7 @@ class TestHostsManager:
manager.edit_mode = True
# Mock permission manager to raise exception
manager.permission_manager.release_sudo = Mock(
manager.permission_manager.clear_permission_state = Mock(
side_effect=Exception("Test error")
)