Add guided env setup modal

This commit is contained in:
Philip Henning 2026-03-09 11:25:50 +01:00
parent c9859a5324
commit 8c90306c75
6 changed files with 474 additions and 9 deletions

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio
import time
from collections import Counter
from pathlib import Path
import pytest
from textual.app import App, ComposeResult
@ -11,8 +12,10 @@ from textual.widgets import Button, Checkbox, Input, Select, Static
from pve_vm_setup.app import PveVmSetupApp
from pve_vm_setup.models.workflow import WorkflowState
from pve_vm_setup.screens.env_setup import EnvSetupModal, MissingEnvSetupPromptModal
from pve_vm_setup.screens.login import LoginView
from pve_vm_setup.screens.wizard import NO_DISK_SELECTED, AutoStartConfirmModal, WizardView
from pve_vm_setup.services.factory import ProxmoxServiceFactory
from pve_vm_setup.services.fake import FakeProxmoxService
from pve_vm_setup.settings import AppSettings
@ -85,6 +88,80 @@ async def test_main_app_mounts_wizard_only_after_login() -> None:
assert app.focused is app.query_one("#general-name", Input)
@pytest.mark.asyncio
async def test_main_app_prompts_for_env_setup_when_no_dotenv_exists(
tmp_path: Path, monkeypatch
) -> None:
home = tmp_path / "home"
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("HOME", str(home))
app = PveVmSetupApp(
AppSettings.from_env({}, load_dotenv_file=True),
prompt_for_missing_env_setup=True,
)
async with app.run_test() as pilot:
await pilot.pause()
assert isinstance(app.screen_stack[-1], MissingEnvSetupPromptModal)
@pytest.mark.asyncio
async def test_main_app_can_save_env_setup_and_refresh_login_mode(
tmp_path: Path, monkeypatch
) -> None:
class LiveLikeService(FakeProxmoxService):
mode = "live"
home = tmp_path / "home"
config_path = home / ".config" / "pve-vm-setup" / ".env"
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("HOME", str(home))
monkeypatch.delenv("PROXMOX_URL", raising=False)
monkeypatch.delenv("PROXMOX_USER", raising=False)
def create_service(settings: AppSettings):
if settings.proxmox_url:
return LiveLikeService()
return FakeProxmoxService()
monkeypatch.setattr(ProxmoxServiceFactory, "create", staticmethod(create_service))
app = PveVmSetupApp(
AppSettings.from_env({}, load_dotenv_file=True),
prompt_for_missing_env_setup=True,
)
async with app.run_test() as pilot:
await pilot.pause()
assert isinstance(app.screen_stack[-1], MissingEnvSetupPromptModal)
app.query_one("#missing-env-setup", Button).press()
await pilot.pause()
assert isinstance(app.screen_stack[-1], EnvSetupModal)
app.query_one("#env-proxmox-url", Input).value = "https://pve.example.invalid:8006"
app.query_one("#env-proxmox-user", Input).value = "root"
app.query_one("#env-setup-save", Button).press()
for _ in range(10):
await pilot.pause(0.05)
if not app.screen_stack or not isinstance(app.screen_stack[-1], EnvSetupModal):
break
assert config_path.exists()
content = config_path.read_text(encoding="utf-8")
assert "PROXMOX_URL=https://pve.example.invalid:8006" in content
assert "PROXMOX_USER=root" in content
assert "PROXMOX_PASSWORD" not in content
assert "Mode: live on pve.example.invalid:8006" == str(
app.query_one("#mode", Static).renderable
)
assert app.query_one("#username", Input).value == "root"
@pytest.mark.asyncio
async def test_wizard_activation_focuses_first_editable_field() -> None:
service = FakeProxmoxService()

View file

@ -3,7 +3,7 @@ from pathlib import Path
import pytest
from pve_vm_setup.errors import SettingsError
from pve_vm_setup.settings import AppSettings
from pve_vm_setup.settings import AppSettings, resolve_dotenv_paths, write_config_dotenv
def test_settings_load_defaults_and_normalize_api_base() -> None:
@ -152,3 +152,37 @@ def test_settings_prefers_environment_over_config_and_current_directory_dotenv(
)
assert settings.proxmox_url == "https://env.example.invalid:8006"
def test_resolve_dotenv_paths_uses_standard_config_location(tmp_path: Path, monkeypatch) -> None:
home = tmp_path / "home"
monkeypatch.setenv("HOME", str(home))
paths = resolve_dotenv_paths()
assert paths.cwd == Path(".env")
assert paths.config == home / ".config" / "pve-vm-setup" / ".env"
assert paths.any_exists is False
def test_write_config_dotenv_creates_parent_directory_and_skips_blank_values(
tmp_path: Path,
) -> None:
target = tmp_path / "home" / ".config" / "pve-vm-setup" / ".env"
written_path = write_config_dotenv(
{
"PROXMOX_URL": "https://pve.example.invalid:8006",
"PROXMOX_USER": "root",
"PROXMOX_PASSWORD": "",
},
config_dotenv_path=target,
)
assert written_path == target
assert target.exists()
assert target.read_text(encoding="utf-8") == (
"# Generated by pve-vm-setup\n"
"PROXMOX_URL=https://pve.example.invalid:8006\n"
"PROXMOX_USER=root\n"
)