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)) try: app.bg_canvas.winfo_width.return_value = 1000 except Exception: try: app.bg_canvas.winfo_width = MagicMock(return_value=1000) except Exception: pass # Ensure deterministic gating for the Cluster Nodes screen (multi-node, non-k3d). try: app.cluster_env.get.return_value = "prole-service-cluster" except Exception: pass app.ansible_topology = { "domain": "prole.org", "hosts": {"merlin": "10.0.0.10", "myrddin": "10.0.0.11"}, "groups": {"k3s_hosts": ["merlin", "myrddin"]}, } return app def test_init_cluster_next_goes_to_cluster_nodes(mock_installer): mock_installer.show_page("init_cluster") with ( patch.object(mock_installer, "_should_show_cluster_nodes_screen", return_value=True), patch.object(mock_installer, "_cluster_ready_for_navigation", return_value=True), patch.object(mock_installer, "_validate_and_save_cluster_config", return_value=True), patch.object(mock_installer, "_run_service_layer_overlay") as mock_overlay, ): mock_installer.on_next() # Service-layer overlay gating has been removed; placement happens in Cluster Nodes. mock_overlay.assert_not_called() assert mock_installer.pages[mock_installer.page_index][0] == "cluster_nodes" def test_cluster_nodes_next_proceeds_to_database_options(mock_installer): mock_installer.show_page("cluster_nodes") mock_installer._cluster_nodes_current_policy = { "hosts": {}, "services": {}, } with patch.object( mock_installer, "_validate_and_save_cluster_nodes_policy", return_value=True, ): mock_installer.on_next() assert mock_installer.pages[mock_installer.page_index][0] == "database_options" def test_cluster_nodes_prev_goes_back_to_cluster_environment(mock_installer): mock_installer.show_page("cluster_nodes") mock_installer.on_prev() assert mock_installer.pages[mock_installer.page_index][0] == "init_cluster"