import sys from unittest.mock import patch, MagicMock import pytest from pathlib import Path # Mock tkinter and other GUI/macOS specific imports sys.modules["tkinter"] = MagicMock() sys.modules["tkinter.ttk"] = MagicMock() sys.modules["tkinter.scrolledtext"] = MagicMock() sys.modules["tkinter.messagebox"] = MagicMock() sys.modules["Foundation"] = MagicMock() sys.modules["AppKit"] = MagicMock() sys.modules["PIL"] = MagicMock() sys.modules["PIL.Image"] = MagicMock() sys.modules["PIL.ImageTk"] = MagicMock() import install from install import ProleInstaller @pytest.fixture def mock_installer(tmp_path): test_cfg_src = Path(__file__).parent / "fixtures" / "test-prole.cfg" cfg_path = tmp_path / "prole.cfg" cfg_path.write_text(test_cfg_src.read_text()) with patch("install.tk.Tk"), patch("install.tk.Frame"), patch( "install.tk.Canvas" ), patch("install.tk.Label"), patch("install.tk.Button"), patch( "install.tk.Entry" ) as mock_entry, patch( "install.tk.StringVar" ), patch( "install.tk.BooleanVar" ), patch( "install.tk.IntVar" ), patch( "install.ttk.Style" ), patch( "install.Path.exists", return_value=True ), patch( "install.Path.mkdir" ), patch.object( ProleInstaller, "_save_env_to_file" ), patch.object( ProleInstaller, "_save_prole_cfg" ), patch.object( ProleInstaller, "_save_ansible_prole_vault" ), patch.object( ProleInstaller, "_after_env_saved" ), patch( "threading.Thread" ): mock_entry.return_value.get.return_value.strip.return_value = "mock_val" root = MagicMock() root.winfo_screenwidth.return_value = 1920 root.winfo_screenheight.return_value = 1080 app = ProleInstaller(root, config_path=str(cfg_path)) app.bg_canvas.winfo_width.return_value = 1000 return app def test_service_layer_navigation_pause(mock_installer): # Go to init_cluster mock_installer.show_page("init_cluster") # Mock cluster ready with patch.object( mock_installer, "_cluster_ready_for_navigation", return_value=True ): # Click Next - should NOT go to init_password yet, but show overlay with patch.object(mock_installer, "_run_service_layer_overlay") as mock_overlay: mock_installer.on_next() mock_overlay.assert_called_once() assert mock_installer.pages[mock_installer.page_index][0] == "init_cluster" def test_service_layer_overlay_behavior(mock_installer): mock_installer.show_page("init_cluster") # Trigger overlay with patch.object( mock_installer, "_cluster_ready_for_navigation", return_value=True ), patch.object(mock_installer, "_create_console_output"): mock_installer.on_next() assert mock_installer._showing_service_overlay is True assert mock_installer._common_services_success is False # Next button should be disabled via update_footer mock_installer.next_button = MagicMock() mock_installer.prev_button = MagicMock() mock_installer.update_footer() mock_installer.next_button.configure.assert_any_call(state="disabled") # Simulate success mock_installer._common_services_success = True mock_installer.next_button.reset_mock() mock_installer.update_footer() mock_installer.next_button.configure.assert_any_call(state="normal") # Now Next should proceed with patch.object( mock_installer, "_cluster_ready_for_navigation", return_value=True ): mock_installer.on_next() assert mock_installer.pages[mock_installer.page_index][0] == "database_options" def test_service_layer_prev_behavior(mock_installer): mock_installer.show_page("init_cluster") with patch.object( mock_installer, "_cluster_ready_for_navigation", return_value=True ), patch.object(mock_installer, "_create_console_output"): mock_installer.on_next() assert mock_installer._showing_service_overlay is True # Prev from overlay should go back to init_cluster and reset overlay flag mock_installer.on_prev() assert mock_installer._showing_service_overlay is False assert mock_installer.pages[mock_installer.page_index][0] == "init_cluster"