diff --git a/src/hosts/core/manager.py b/src/hosts/core/manager.py index e3951e4..a1230fe 100644 --- a/src/hosts/core/manager.py +++ b/src/hosts/core/manager.py @@ -296,7 +296,18 @@ class HostsManager: try: # Add the new entry at the end hosts_file.entries.append(entry) - return True, "Entry added successfully" + + # Save the file immediately + save_success, save_message = self.save_hosts_file(hosts_file) + if not save_success: + # If save fails, remove the entry that was just added + hosts_file.entries.pop() + return False, f"Failed to save after adding entry: {save_message}" + + canonical_hostname = ( + entry.hostnames[0] if entry.hostnames else entry.ip_address + ) + return True, f"Entry added: {canonical_hostname}" except Exception as e: return False, f"Error adding entry: {e}" diff --git a/src/hosts/tui/app.py b/src/hosts/tui/app.py index 270d707..520dfdb 100644 --- a/src/hosts/tui/app.py +++ b/src/hosts/tui/app.py @@ -7,7 +7,7 @@ all the handlers and provides the primary user interface. from textual.app import App, ComposeResult from textual.containers import Horizontal, Vertical -from textual.widgets import Header, Static, DataTable, Input, Checkbox, Label +from textual.widgets import Header, Static, DataTable, Input, Checkbox from textual.reactive import reactive from ..core.parser import HostsParser @@ -94,7 +94,9 @@ class HostsManagerApp(App): # Details display form (disabled inputs) with Vertical(id="entry-details-display", classes="entry-form"): - with Vertical(classes="default-section section-no-top-margin") as ip_address: + with Vertical( + classes="default-section section-no-top-margin" + ) as ip_address: ip_address.border_title = "IP Address" yield Input( placeholder="No entry selected", @@ -118,18 +120,23 @@ class HostsManagerApp(App): placeholder="No entry selected", id="details-comment-input", disabled=True, - classes="default-input", - ) + classes="default-input", + ) with Vertical(classes="default-section") as active: active.border_title = "Active" yield Checkbox( - "Active", id="details-active-checkbox", disabled=True, classes="default-checkbox" + "Active", + id="details-active-checkbox", + disabled=True, + classes="default-checkbox", ) # Edit form (initially hidden) with Vertical(id="entry-edit-form", classes="entry-form hidden"): - with Vertical(classes="default-section section-no-top-margin") as ip_address: + with Vertical( + classes="default-section section-no-top-margin" + ) as ip_address: ip_address.border_title = "IP Address" yield Input( placeholder="Enter IP address", @@ -151,11 +158,13 @@ class HostsManagerApp(App): placeholder="Enter comment (optional)", id="comment-input", classes="default-input", - ) + ) with Vertical(classes="default-section") as active: active.border_title = "Active" - yield Checkbox("Active", id="active-checkbox", classes="default-checkbox") + yield Checkbox( + "Active", id="active-checkbox", classes="default-checkbox" + ) # Status bar for error/temporary messages (overlay, doesn't affect layout) yield Static("", id="status-bar", classes="status-bar hidden") diff --git a/tests/test_manager.py b/tests/test_manager.py index c1d0657..ae5f6c0 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -634,3 +634,58 @@ class TestHostsManager: finally: # Clean up Path(temp_path).unlink() + + def test_add_entry_success(self): + """Test successfully adding an entry with immediate save.""" + manager = HostsManager() + manager.edit_mode = True + + # Mock the save operation to succeed + with patch.object(manager, "save_hosts_file") as mock_save: + mock_save.return_value = (True, "File saved successfully") + + hosts_file = HostsFile() + test_entry = HostEntry(ip_address="192.168.1.100", hostnames=["testhost"]) + + success, message = manager.add_entry(hosts_file, test_entry) + + assert success + assert "Entry added: testhost" in message + assert len(hosts_file.entries) == 1 + assert hosts_file.entries[0] == test_entry + mock_save.assert_called_once_with(hosts_file) + + def test_add_entry_save_failure(self): + """Test adding an entry when save fails.""" + manager = HostsManager() + manager.edit_mode = True + + # Mock the save operation to fail + with patch.object(manager, "save_hosts_file") as mock_save: + mock_save.return_value = (False, "Permission denied") + + hosts_file = HostsFile() + test_entry = HostEntry(ip_address="192.168.1.100", hostnames=["testhost"]) + + success, message = manager.add_entry(hosts_file, test_entry) + + assert not success + assert "Failed to save after adding entry" in message + assert ( + len(hosts_file.entries) == 0 + ) # Entry should be removed on save failure + mock_save.assert_called_once_with(hosts_file) + + def test_add_entry_not_in_edit_mode(self): + """Test adding an entry when not in edit mode.""" + manager = HostsManager() + # edit_mode defaults to False + + hosts_file = HostsFile() + test_entry = HostEntry(ip_address="192.168.1.100", hostnames=["testhost"]) + + success, message = manager.add_entry(hosts_file, test_entry) + + assert not success + assert "Not in edit mode" in message + assert len(hosts_file.entries) == 0