3e779be6f3
CI / lint (push) Failing after 13m4s
CI / test (3.11, ubuntu-latest) (push) Failing after 2m4s
CI / test (3.13, ubuntu-latest) (push) Successful in 13m30s
CI / test (3.14, ubuntu-latest) (push) Successful in 17m21s
CI / test (3.12, ubuntu-latest) (push) Successful in 17m55s
CI / discover-apps-ps (push) Successful in 1m56s
CI / test (3.9, ubuntu-latest) (push) Successful in 13m17s
CI / test (3.10, ubuntu-latest) (push) Successful in 26m21s
CI / audit (push) Successful in 13m38s
Deploy site / deploy (push) Has been cancelled
CI / test (3.14, ubuntu-24.04-arm) (push) Has been cancelled
187 lines
6.5 KiB
Python
187 lines
6.5 KiB
Python
# SPDX-License-Identifier: MIT
|
|
"""Tests for ``cfg.install`` (agent-first install flow).
|
|
|
|
See ``docs/design/AGENT_FIRST_INSTALL_DESIGN.md`` §"Config schema
|
|
additions" for the schema this guards.
|
|
"""
|
|
|
|
from winpodx.core.config import Config, InstallConfig
|
|
|
|
# --- defaults -----------------------------------------------------------------
|
|
|
|
|
|
def test_install_config_defaults():
|
|
cfg = InstallConfig()
|
|
assert cfg.agent_first is False
|
|
assert cfg.wait_ready_stage2_secs == 900
|
|
assert cfg.wait_ready_stage3_secs == 1800
|
|
assert cfg.auto_resume is True
|
|
assert cfg.watchdog_max_respawns == 3
|
|
assert cfg.watchdog_probe_debounce_count == 2
|
|
assert cfg.watchdog_probe_debounce_secs == [2, 5]
|
|
|
|
|
|
def test_config_includes_install_section():
|
|
cfg = Config()
|
|
assert isinstance(cfg.install, InstallConfig)
|
|
assert cfg.install.agent_first is False
|
|
|
|
|
|
# --- bool coercion ------------------------------------------------------------
|
|
|
|
|
|
def test_install_config_bool_fields_coerced_from_truthy_int():
|
|
cfg = InstallConfig(agent_first=1, auto_resume=0)
|
|
assert cfg.agent_first is True
|
|
assert cfg.auto_resume is False
|
|
|
|
|
|
def test_install_config_bool_fields_coerced_from_arbitrary_string():
|
|
# `bool("maybe")` is True; the point is we don't crash on weird values.
|
|
cfg = InstallConfig(agent_first="maybe")
|
|
assert cfg.agent_first is True
|
|
|
|
|
|
# --- int clamping -------------------------------------------------------------
|
|
|
|
|
|
def test_install_config_wait_ready_clamping():
|
|
cfg = InstallConfig(wait_ready_stage2_secs=-5)
|
|
assert cfg.wait_ready_stage2_secs == 60
|
|
|
|
cfg = InstallConfig(wait_ready_stage2_secs=0)
|
|
assert cfg.wait_ready_stage2_secs == 60
|
|
|
|
cfg = InstallConfig(wait_ready_stage2_secs=99999)
|
|
assert cfg.wait_ready_stage2_secs == 14400
|
|
|
|
|
|
def test_install_config_wait_ready_stage3_clamping():
|
|
cfg = InstallConfig(wait_ready_stage3_secs=-1)
|
|
assert cfg.wait_ready_stage3_secs == 60
|
|
cfg = InstallConfig(wait_ready_stage3_secs=99999)
|
|
assert cfg.wait_ready_stage3_secs == 14400
|
|
|
|
|
|
def test_install_config_watchdog_int_clamping():
|
|
cfg = InstallConfig(watchdog_max_respawns=-1, watchdog_probe_debounce_count=0)
|
|
assert cfg.watchdog_max_respawns == 0
|
|
assert cfg.watchdog_probe_debounce_count == 1
|
|
|
|
|
|
# --- watchdog_probe_debounce_secs list coercion -------------------------------
|
|
|
|
|
|
def test_install_config_debounce_secs_string_falls_back_to_default():
|
|
cfg = InstallConfig(watchdog_probe_debounce_secs="not-a-list") # type: ignore[arg-type]
|
|
assert cfg.watchdog_probe_debounce_secs == [2, 5]
|
|
|
|
|
|
def test_install_config_debounce_secs_empty_falls_back_to_default():
|
|
cfg = InstallConfig(watchdog_probe_debounce_secs=[])
|
|
assert cfg.watchdog_probe_debounce_secs == [2, 5]
|
|
|
|
|
|
def test_install_config_debounce_secs_negative_falls_back_to_default():
|
|
cfg = InstallConfig(watchdog_probe_debounce_secs=[2, -3])
|
|
assert cfg.watchdog_probe_debounce_secs == [2, 5]
|
|
|
|
|
|
def test_install_config_debounce_secs_garbage_element_falls_back_to_default():
|
|
cfg = InstallConfig(watchdog_probe_debounce_secs=[2, "five"]) # type: ignore[list-item]
|
|
assert cfg.watchdog_probe_debounce_secs == [2, 5]
|
|
|
|
|
|
def test_install_config_debounce_secs_custom_value_preserved():
|
|
cfg = InstallConfig(watchdog_probe_debounce_secs=[1, 3, 7])
|
|
assert cfg.watchdog_probe_debounce_secs == [1, 3, 7]
|
|
|
|
|
|
def test_install_config_debounce_secs_default_is_independent_per_instance():
|
|
a = InstallConfig()
|
|
b = InstallConfig()
|
|
a.watchdog_probe_debounce_secs.append(99)
|
|
assert b.watchdog_probe_debounce_secs == [2, 5]
|
|
|
|
|
|
# --- TOML round-trip ----------------------------------------------------------
|
|
|
|
|
|
def test_config_load_with_no_install_section_uses_defaults(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
|
|
|
|
path = Config.path()
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text('[rdp]\nuser = "alice"\n', encoding="utf-8")
|
|
|
|
loaded = Config.load()
|
|
assert loaded.rdp.user == "alice"
|
|
assert loaded.install.agent_first is False
|
|
assert loaded.install.wait_ready_stage2_secs == 900
|
|
assert loaded.install.watchdog_probe_debounce_secs == [2, 5]
|
|
|
|
|
|
def test_config_save_load_install_round_trip(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
|
|
|
|
cfg = Config()
|
|
cfg.install.agent_first = True
|
|
cfg.install.wait_ready_stage2_secs = 1200
|
|
cfg.install.wait_ready_stage3_secs = 2400
|
|
cfg.install.auto_resume = False
|
|
cfg.install.watchdog_max_respawns = 5
|
|
cfg.install.watchdog_probe_debounce_count = 3
|
|
cfg.install.watchdog_probe_debounce_secs = [1, 2, 4]
|
|
cfg.save()
|
|
|
|
loaded = Config.load()
|
|
assert loaded.install.agent_first is True
|
|
assert loaded.install.wait_ready_stage2_secs == 1200
|
|
assert loaded.install.wait_ready_stage3_secs == 2400
|
|
assert loaded.install.auto_resume is False
|
|
assert loaded.install.watchdog_max_respawns == 5
|
|
assert loaded.install.watchdog_probe_debounce_count == 3
|
|
assert loaded.install.watchdog_probe_debounce_secs == [1, 2, 4]
|
|
|
|
|
|
def test_config_load_revalidates_install_values(tmp_path, monkeypatch):
|
|
"""A hand-edited TOML with bad values must coerce, not crash."""
|
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
|
|
|
|
path = Config.path()
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(
|
|
"[install]\n"
|
|
'agent_first = "maybe"\n'
|
|
"wait_ready_stage2_secs = -5\n"
|
|
"wait_ready_stage3_secs = 99999\n"
|
|
"watchdog_max_respawns = -10\n"
|
|
"watchdog_probe_debounce_count = 0\n"
|
|
"watchdog_probe_debounce_secs = [-1, 2]\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
loaded = Config.load()
|
|
# `bool("maybe")` is True — what matters is we don't raise.
|
|
assert isinstance(loaded.install.agent_first, bool)
|
|
assert loaded.install.wait_ready_stage2_secs == 60
|
|
assert loaded.install.wait_ready_stage3_secs == 14400
|
|
assert loaded.install.watchdog_max_respawns == 0
|
|
assert loaded.install.watchdog_probe_debounce_count == 1
|
|
assert loaded.install.watchdog_probe_debounce_secs == [2, 5]
|
|
|
|
|
|
def test_config_load_install_secs_string_in_toml_falls_back(tmp_path, monkeypatch):
|
|
"""``watchdog_probe_debounce_secs = "oops"`` must not crash."""
|
|
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
|
|
|
|
path = Config.path()
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(
|
|
'[install]\nwatchdog_probe_debounce_secs = "oops"\n',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
loaded = Config.load()
|
|
assert loaded.install.watchdog_probe_debounce_secs == [2, 5]
|