""" Tests for the main TUI application. This module contains unit tests for the HostsManagerApp class, validating application behavior, navigation, and user interactions. """ from unittest.mock import Mock, patch from hosts.tui.app import HostsManagerApp from hosts.core.models import HostEntry, HostsFile from hosts.core.parser import HostsParser from hosts.core.config import Config class TestHostsManagerApp: """Test cases for the HostsManagerApp class.""" def test_app_initialization(self): """Test application initialization.""" with patch("hosts.tui.app.HostsParser"), patch("hosts.tui.app.Config"): app = HostsManagerApp() assert app.title == "/etc/hosts Manager" assert app.sub_title == "" # Now set by update_status assert app.edit_mode is False assert app.selected_entry_index == 0 assert app.sort_column == "" assert app.sort_ascending is True def test_app_compose_method_exists(self): """Test that app has compose method.""" with patch("hosts.tui.app.HostsParser"), patch("hosts.tui.app.Config"): app = HostsManagerApp() # Test that compose method exists and is callable assert hasattr(app, "compose") assert callable(app.compose) def test_load_hosts_file_success(self): """Test successful hosts file loading.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) # Create test hosts file test_hosts = HostsFile() test_entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]) test_hosts.add_entry(test_entry) mock_parser.parse.return_value = test_hosts mock_parser.get_file_info.return_value = { "path": "/etc/hosts", "exists": True, "size": 100, } with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.populate_entries_table = Mock() app.update_entry_details = Mock() app.set_timer = Mock() app.load_hosts_file() # Verify hosts file was loaded assert len(app.hosts_file.entries) == 1 assert app.hosts_file.entries[0].ip_address == "127.0.0.1" mock_parser.parse.assert_called_once() def test_load_hosts_file_not_found(self): """Test handling of missing hosts file.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) mock_parser.parse.side_effect = FileNotFoundError("Hosts file not found") with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.update_status = Mock() app.load_hosts_file() # Should handle error gracefully app.update_status.assert_called_with( "❌ Error loading hosts file: Hosts file not found" ) def test_load_hosts_file_permission_error(self): """Test handling of permission denied error.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) mock_parser.parse.side_effect = PermissionError("Permission denied") with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.update_status = Mock() app.load_hosts_file() # Should handle error gracefully app.update_status.assert_called_with( "❌ Error loading hosts file: Permission denied" ) def test_populate_entries_table_logic(self): """Test populating DataTable logic without UI dependencies.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) mock_config.should_show_default_entries.return_value = True mock_config.is_default_entry.return_value = False with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Mock the query_one method to return a mock table mock_table = Mock() app.query_one = Mock(return_value=mock_table) # Add test entries app.hosts_file = HostsFile() active_entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]) inactive_entry = HostEntry( ip_address="192.168.1.1", hostnames=["router"], is_active=False ) app.hosts_file.add_entry(active_entry) app.hosts_file.add_entry(inactive_entry) app.populate_entries_table() # Verify table methods were called mock_table.clear.assert_called_once_with(columns=True) mock_table.add_columns.assert_called_once() assert mock_table.add_row.call_count == 2 # Two entries added def test_update_entry_details_with_entry(self): """Test updating entry details pane.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) mock_config.should_show_default_entries.return_value = True with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Mock the query_one method to return disabled input widgets mock_details_display = Mock() mock_edit_form = Mock() mock_ip_input = Mock() mock_hostname_input = Mock() mock_comment_input = Mock() mock_active_checkbox = Mock() def mock_query_one(selector, widget_type=None): if selector == "#entry-details-display": return mock_details_display elif selector == "#entry-edit-form": return mock_edit_form elif selector == "#details-ip-input": return mock_ip_input elif selector == "#details-hostname-input": return mock_hostname_input elif selector == "#details-comment-input": return mock_comment_input elif selector == "#details-active-checkbox": return mock_active_checkbox return Mock() app.query_one = mock_query_one # Add test entry app.hosts_file = HostsFile() test_entry = HostEntry( ip_address="127.0.0.1", hostnames=["localhost", "local"], comment="Test comment", ) app.hosts_file.add_entry(test_entry) app.selected_entry_index = 0 app.update_entry_details() # Verify input widgets were updated with entry data mock_details_display.remove_class.assert_called_with("hidden") mock_edit_form.add_class.assert_called_with("hidden") assert mock_ip_input.value == "127.0.0.1" assert mock_hostname_input.value == "localhost, local" assert mock_comment_input.value == "Test comment" assert mock_active_checkbox.value def test_update_entry_details_no_entries(self): """Test updating entry details with no entries.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Mock the query_one method to return disabled input widgets mock_details_display = Mock() mock_edit_form = Mock() mock_ip_input = Mock() mock_hostname_input = Mock() mock_comment_input = Mock() mock_active_checkbox = Mock() def mock_query_one(selector, widget_type=None): if selector == "#entry-details-display": return mock_details_display elif selector == "#entry-edit-form": return mock_edit_form elif selector == "#details-ip-input": return mock_ip_input elif selector == "#details-hostname-input": return mock_hostname_input elif selector == "#details-comment-input": return mock_comment_input elif selector == "#details-active-checkbox": return mock_active_checkbox return Mock() app.query_one = mock_query_one app.hosts_file = HostsFile() app.update_entry_details() # Verify widgets show empty state placeholders mock_details_display.remove_class.assert_called_with("hidden") mock_edit_form.add_class.assert_called_with("hidden") assert mock_ip_input.value == "" assert mock_ip_input.placeholder == "No entries loaded" assert mock_hostname_input.value == "" assert mock_hostname_input.placeholder == "No entries loaded" assert mock_comment_input.value == "" assert mock_comment_input.placeholder == "No entries loaded" assert not mock_active_checkbox.value def test_update_status_default(self): """Test status bar update with default information.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) mock_parser.get_file_info.return_value = { "path": "/etc/hosts", "exists": True, "size": 100, } with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Mock the query_one method to avoid UI dependencies mock_footer = Mock() app.query_one = Mock(return_value=mock_footer) # Add test entries app.hosts_file = HostsFile() app.hosts_file.add_entry( HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]) ) app.hosts_file.add_entry( HostEntry( ip_address="192.168.1.1", hostnames=["router"], is_active=False ) ) app.update_status() # Verify footer status was updated mock_footer.set_status.assert_called_once() status_call = mock_footer.set_status.call_args[0][0] assert "Read-only" in status_call assert "2 entries" in status_call assert "1 active" in status_call def test_update_status_custom_message(self): """Test status bar update with custom message.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Mock set_timer and query_one to avoid event loop and UI issues app.set_timer = Mock() mock_status_bar = Mock() mock_footer = Mock() def mock_query_one(selector, widget_type=None): if selector == "#status-bar": return mock_status_bar elif selector == "#custom-footer": return mock_footer return Mock() app.query_one = mock_query_one # Add test hosts_file for footer status generation app.hosts_file = HostsFile() app.hosts_file.add_entry( HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]) ) app.hosts_file.add_entry( HostEntry( ip_address="192.168.1.1", hostnames=["router"], is_active=False ) ) app.update_status("Custom status message") # Verify status bar was updated with custom message mock_status_bar.update.assert_called_with("Custom status message") mock_status_bar.remove_class.assert_called_with("hidden") # Verify footer status was updated with current status (not the custom message) mock_footer.set_status.assert_called_once() footer_status = mock_footer.set_status.call_args[0][0] assert "2 entries" in footer_status assert "Read-only" in footer_status # Verify timer was set for auto-clearing app.set_timer.assert_called_once() def test_action_reload(self): """Test reload action.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.load_hosts_file = Mock() app.update_status = Mock() app.action_reload() app.load_hosts_file.assert_called_once() app.update_status.assert_called_with("Hosts file reloaded") def test_action_help(self): """Test help action.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.action_show_help_panel = Mock() app.action_help() # Should call the built-in help action app.action_show_help_panel.assert_called_once() def test_action_config(self): """Test config action opens modal.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.push_screen = Mock() app.action_config() # Should push config modal screen app.push_screen.assert_called_once() args = app.push_screen.call_args[0] assert len(args) >= 1 # ConfigModal instance def test_action_sort_by_ip_ascending(self): """Test sorting by IP address in ascending order.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Add test entries in reverse order app.hosts_file = HostsFile() app.hosts_file.add_entry( HostEntry(ip_address="192.168.1.1", hostnames=["router"]) ) app.hosts_file.add_entry( HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]) ) app.hosts_file.add_entry( HostEntry(ip_address="10.0.0.1", hostnames=["test"]) ) # Mock the table_handler methods to avoid UI queries app.table_handler.populate_entries_table = Mock() app.table_handler.restore_cursor_position = Mock() app.update_status = Mock() app.action_sort_by_ip() # Check that entries are sorted by IP address assert app.hosts_file.entries[0].ip_address == "10.0.0.1" # Sorted by IP assert app.hosts_file.entries[1].ip_address == "127.0.0.1" assert app.hosts_file.entries[2].ip_address == "192.168.1.1" assert app.sort_column == "ip" assert app.sort_ascending is True app.table_handler.populate_entries_table.assert_called_once() def test_action_sort_by_hostname_ascending(self): """Test sorting by hostname in ascending order.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Add test entries in reverse alphabetical order app.hosts_file = HostsFile() app.hosts_file.add_entry( HostEntry(ip_address="127.0.0.1", hostnames=["zebra"]) ) app.hosts_file.add_entry( HostEntry(ip_address="192.168.1.1", hostnames=["alpha"]) ) app.hosts_file.add_entry( HostEntry(ip_address="10.0.0.1", hostnames=["beta"]) ) # Mock the table_handler methods to avoid UI queries app.table_handler.populate_entries_table = Mock() app.table_handler.restore_cursor_position = Mock() app.update_status = Mock() app.action_sort_by_hostname() # Check that entries are sorted alphabetically assert app.hosts_file.entries[0].hostnames[0] == "alpha" assert app.hosts_file.entries[1].hostnames[0] == "beta" assert app.hosts_file.entries[2].hostnames[0] == "zebra" assert app.sort_column == "hostname" assert app.sort_ascending is True app.table_handler.populate_entries_table.assert_called_once() def test_data_table_row_highlighted_event(self): """Test DataTable row highlighting event handling.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Mock the details_handler and table_handler methods app.details_handler.update_entry_details = Mock() app.table_handler.display_index_to_actual_index = Mock(return_value=2) # Create mock event with required parameters mock_table = Mock() mock_table.id = "entries-table" event = Mock() event.data_table = mock_table event.cursor_row = 2 app.on_data_table_row_highlighted(event) # Should update selected index and details assert app.selected_entry_index == 2 app.details_handler.update_entry_details.assert_called_once() app.table_handler.display_index_to_actual_index.assert_called_once_with(2) def test_data_table_header_selected_ip_column(self): """Test DataTable header selection for IP column.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.action_sort_by_ip = Mock() # Create mock event for IP column mock_table = Mock() mock_table.id = "entries-table" event = Mock() event.data_table = mock_table event.column_key = "IP Address" app.on_data_table_header_selected(event) app.action_sort_by_ip.assert_called_once() def test_restore_cursor_position_logic(self): """Test cursor position restoration logic.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Mock the query_one method to avoid UI dependencies mock_table = Mock() app.query_one = Mock(return_value=mock_table) app.update_entry_details = Mock() # Add test entries app.hosts_file = HostsFile() entry1 = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]) entry2 = HostEntry(ip_address="192.168.1.1", hostnames=["router"]) app.hosts_file.add_entry(entry1) app.hosts_file.add_entry(entry2) # Test the logic without UI dependencies # Find the index of entry2 target_index = None for i, entry in enumerate(app.hosts_file.entries): if ( entry.ip_address == entry2.ip_address and entry.hostnames == entry2.hostnames ): target_index = i break # Should find the matching entry at index 1 assert target_index == 1 def test_app_bindings_defined(self): """Test that application has expected key bindings.""" with patch("hosts.tui.app.HostsParser"), patch("hosts.tui.app.Config"): app = HostsManagerApp() # Check that bindings are defined assert len(app.BINDINGS) >= 6 # Check specific bindings exist (handle both Binding objects and tuples) binding_keys = [] for binding in app.BINDINGS: if hasattr(binding, "key"): # Binding object binding_keys.append(binding.key) elif isinstance(binding, tuple) and len(binding) >= 1: # Tuple format (key, action, description) binding_keys.append(binding[0]) assert "q" in binding_keys assert "r" in binding_keys assert "question_mark" in binding_keys # Help binding (? key) assert "i" in binding_keys assert "n" in binding_keys assert "c" in binding_keys assert "ctrl+c" in binding_keys def test_radio_set_event_handling_ip_entry(self): """Test radio set event handling for IP entry type.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.edit_handler.handle_entry_type_change = Mock() # Create mock radio set event for IP entry mock_radio_set = Mock() mock_radio_set.id = "edit-entry-type-radio" mock_pressed_radio = Mock() mock_pressed_radio.id = "edit-ip-entry-radio" event = Mock() event.radio_set = mock_radio_set event.pressed = mock_pressed_radio app.on_radio_set_changed(event) # Should handle IP entry type change app.edit_handler.handle_entry_type_change.assert_called_once_with("ip") def test_radio_set_event_handling_dns_entry(self): """Test radio set event handling for DNS entry type.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.edit_handler.handle_entry_type_change = Mock() # Create mock radio set event for DNS entry mock_radio_set = Mock() mock_radio_set.id = "edit-entry-type-radio" mock_pressed_radio = Mock() mock_pressed_radio.id = "edit-dns-entry-radio" event = Mock() event.radio_set = mock_radio_set event.pressed = mock_pressed_radio app.on_radio_set_changed(event) # Should handle DNS entry type change app.edit_handler.handle_entry_type_change.assert_called_once_with("dns") def test_entry_type_detection_ip_entry(self): """Test entry type detection for IP entries.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Add IP entry (no DNS name) app.hosts_file = HostsFile() ip_entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]) app.hosts_file.add_entry(ip_entry) app.selected_entry_index = 0 entry_type = app.edit_handler.get_current_entry_type() assert entry_type == "ip" def test_entry_type_detection_dns_entry(self): """Test entry type detection for DNS entries.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Add DNS entry with DNS name app.hosts_file = HostsFile() dns_entry = HostEntry(ip_address="0.0.0.0", hostnames=["example"]) dns_entry.dns_name = "example.com" app.hosts_file.add_entry(dns_entry) app.selected_entry_index = 0 entry_type = app.edit_handler.get_current_entry_type() assert entry_type == "dns" def test_field_visibility_ip_type(self): """Test field visibility logic for IP entry type.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Mock the section elements mock_ip_section = Mock() mock_dns_section = Mock() def mock_query_one(selector): if selector == "#edit-ip-section": return mock_ip_section elif selector == "#edit-dns-section": return mock_dns_section return Mock() app.query_one = mock_query_one app.edit_handler.update_field_visibility(show_ip=True, show_dns=False) # IP section should be visible, DNS section hidden mock_ip_section.remove_class.assert_called_with("hidden") mock_dns_section.add_class.assert_called_with("hidden") def test_field_visibility_dns_type(self): """Test field visibility logic for DNS entry type.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Mock the section elements mock_ip_section = Mock() mock_dns_section = Mock() def mock_query_one(selector): if selector == "#edit-ip-section": return mock_ip_section elif selector == "#edit-dns-section": return mock_dns_section return Mock() app.query_one = mock_query_one app.edit_handler.update_field_visibility(show_ip=False, show_dns=True) # DNS section should be visible, IP section hidden mock_ip_section.add_class.assert_called_with("hidden") mock_dns_section.remove_class.assert_called_with("hidden") def test_populate_edit_form_with_ip_type_detection(self): """Test edit form population with IP type detection.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.entry_edit_mode = True app.set_timer = Mock() # Mock set_timer to avoid event loop issues # Add IP entry app.hosts_file = HostsFile() ip_entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]) app.hosts_file.add_entry(ip_entry) app.selected_entry_index = 0 # Mock radio set and buttons mock_radio_set = Mock() mock_ip_radio = Mock() mock_dns_radio = Mock() def mock_query_one(selector): if selector == "#edit-entry-type-radio": return mock_radio_set elif selector == "#edit-ip-entry-radio": return mock_ip_radio elif selector == "#edit-dns-entry-radio": return mock_dns_radio return Mock() app.query_one = mock_query_one app.edit_handler.handle_entry_type_change = Mock() # Test that the method can be called without errors try: app.edit_handler.populate_edit_form_with_type_detection() # Method executed successfully assert True except Exception as e: # Method should not raise exceptions assert False, f"Method raised unexpected exception: {e}" def test_populate_edit_form_with_dns_type_detection(self): """Test edit form population with DNS type detection.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.entry_edit_mode = True # Add DNS entry app.hosts_file = HostsFile() dns_entry = HostEntry(ip_address="0.0.0.0", hostnames=["example"]) dns_entry.dns_name = "example.com" app.hosts_file.add_entry(dns_entry) app.selected_entry_index = 0 # Mock radio set, buttons, and DNS input with proper value tracking mock_radio_set = Mock() mock_ip_radio = Mock() mock_dns_radio = Mock() # Use a simple object to track value assignment class MockDNSInput: def __init__(self): self.value = "" mock_dns_input = MockDNSInput() def mock_query_one(selector, widget_type=None): if selector == "#edit-entry-type-radio": return mock_radio_set elif selector == "#edit-ip-entry-radio": return mock_ip_radio elif selector == "#edit-dns-entry-radio": return mock_dns_radio elif selector == "#dns-name-input": return mock_dns_input return Mock() app.query_one = mock_query_one app.edit_handler.handle_entry_type_change = Mock() app.edit_handler.populate_edit_form_with_type_detection() # Should set DNS radio button as pressed and populate DNS field assert mock_radio_set.pressed_button == mock_dns_radio assert mock_dns_input.value == "example.com" app.edit_handler.handle_entry_type_change.assert_called_with("dns") def test_edit_form_initialization_calls_type_detection(self): """Test that edit form initialization calls type detection.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() # Mock form elements mock_details_display = Mock() mock_edit_form = Mock() mock_ip_input = Mock() mock_hostname_input = Mock() mock_comment_input = Mock() mock_active_checkbox = Mock() def mock_query_one(selector, widget_type=None): if selector == "#entry-details-display": return mock_details_display elif selector == "#entry-edit-form": return mock_edit_form elif selector == "#ip-input": return mock_ip_input elif selector == "#hostname-input": return mock_hostname_input elif selector == "#comment-input": return mock_comment_input elif selector == "#active-checkbox": return mock_active_checkbox return Mock() app.query_one = mock_query_one # Add test entry app.hosts_file = HostsFile() test_entry = HostEntry(ip_address="127.0.0.1", hostnames=["localhost"]) app.hosts_file.add_entry(test_entry) app.selected_entry_index = 0 # Mock the type detection method app.edit_handler.populate_edit_form_with_type_detection = Mock() app.details_handler.update_edit_form() # Should call type detection method app.edit_handler.populate_edit_form_with_type_detection.assert_called_once() def test_dns_resolution_restricted_to_edit_mode(self): """Test that DNS resolution is only allowed in edit mode.""" mock_parser = Mock(spec=HostsParser) mock_config = Mock(spec=Config) with ( patch("hosts.tui.app.HostsParser", return_value=mock_parser), patch("hosts.tui.app.Config", return_value=mock_config), ): app = HostsManagerApp() app.update_status = Mock() # Add test DNS entry app.hosts_file = HostsFile() dns_entry = HostEntry(ip_address="0.0.0.0", hostnames=["example"]) dns_entry.dns_name = "example.com" app.hosts_file.add_entry(dns_entry) app.selected_entry_index = 0 # Test 1: DNS resolution blocked in read-only mode (default) assert app.edit_mode is False # Test action_refresh_dns in read-only mode app.action_refresh_dns() app.update_status.assert_called_with( "❌ Cannot resolve DNS names: Application is in read-only mode. Press 'Ctrl+E' to enable edit mode." ) # Reset mock app.update_status.reset_mock() # Test action_update_single_dns in read-only mode app.action_update_single_dns() app.update_status.assert_called_with( "❌ Cannot resolve DNS names: Application is in read-only mode. Press 'Ctrl+E' to enable edit mode." ) # Test 2: DNS resolution allowed in edit mode app.edit_mode = True app.update_status.reset_mock() # Mock DNS service and other dependencies app.dns_service.resolve_entry_async = Mock() app.manager.save_hosts_file = Mock(return_value=(True, "Success")) app.table_handler.populate_entries_table = Mock() app.details_handler.update_entry_details = Mock() app.run_worker = Mock() # Test action_refresh_dns in edit mode - should proceed app.action_refresh_dns() # Should not show error message about read-only mode error_calls = [call for call in app.update_status.call_args_list if "read-only mode" in str(call)] assert len(error_calls) == 0 # Should start DNS resolution app.run_worker.assert_called() # Reset mocks app.update_status.reset_mock() app.run_worker.reset_mock() # Test action_update_single_dns in edit mode - should proceed app.action_update_single_dns() # Should not show error message about read-only mode error_calls = [call for call in app.update_status.call_args_list if "read-only mode" in str(call)] assert len(error_calls) == 0 # Should start DNS resolution app.run_worker.assert_called() def test_main_function(self): """Test main entry point function.""" with patch("hosts.main.HostsManagerApp") as mock_app_class: mock_app = Mock() mock_app_class.return_value = mock_app from hosts.main import main main() # Should create and run app mock_app_class.assert_called_once() mock_app.run.assert_called_once()