Files
2026-07-13 13:12:33 +08:00

309 lines
11 KiB
Python

"""Shared legacy-home detection for the Phase 3 advisory surfaces.
``detect_legacy_home`` is consumed by the gateway boot warning, the doctor
``migration`` surface, and ``onboarding.status``; these tests pin its guard
behavior (never the live home, portable enumeration only where portable data
dirs can exist, never raises) and the suggested-command rendering.
"""
from __future__ import annotations
import os
import sys
import time
from pathlib import Path
import pytest
from opensquilla.migration import opensquilla_home
from opensquilla.migration.legacy_detect import (
LegacyHomeCandidate,
detect_legacy_home,
suggested_migrate_command,
)
def _make_home(path: Path) -> Path:
path.mkdir(parents=True)
(path / "config.toml").write_text("port = 18790\n", encoding="utf-8")
return path
@pytest.fixture()
def _no_portable_bases(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("LOCALAPPDATA", raising=False)
monkeypatch.delenv("TEMP", raising=False)
def test_detects_cli_home_when_target_is_elsewhere(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
_no_portable_bases: None,
) -> None:
fake_home = tmp_path / "userhome"
legacy = _make_home(fake_home / ".opensquilla")
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
candidate = detect_legacy_home(tmp_path / "electron-home")
assert candidate == LegacyHomeCandidate(path=legacy, kind="cli-home")
def test_live_cli_home_is_never_offered(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
_no_portable_bases: None,
) -> None:
fake_home = tmp_path / "userhome"
legacy = _make_home(fake_home / ".opensquilla")
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
assert detect_legacy_home(legacy) is None
def test_default_target_honors_state_dir_env(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
_no_portable_bases: None,
) -> None:
fake_home = tmp_path / "userhome"
legacy = _make_home(fake_home / ".opensquilla")
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
# Relocated install (e.g. a desktop spawn): ~/.opensquilla is a candidate.
monkeypatch.setenv("OPENSQUILLA_STATE_DIR", str(tmp_path / "relocated-home"))
relocated = detect_legacy_home()
assert relocated == LegacyHomeCandidate(path=legacy, kind="cli-home")
# Default install: the target IS ~/.opensquilla, so nothing is offered.
monkeypatch.setenv("OPENSQUILLA_STATE_DIR", str(legacy))
assert detect_legacy_home() is None
def test_cli_home_precedes_desktop_home(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
_no_portable_bases: None,
) -> None:
fake_home = tmp_path / "userhome"
legacy = _make_home(fake_home / ".opensquilla")
desktop = _make_home(tmp_path / "desktop-home")
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
monkeypatch.setattr(opensquilla_home, "detect_desktop_home", lambda: desktop)
candidate = detect_legacy_home(tmp_path / "target-home")
assert candidate == LegacyHomeCandidate(path=legacy, kind="cli-home")
def test_desktop_home_precedes_portable_and_preserves_kind(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
fake_home = tmp_path / "userhome"
fake_home.mkdir() # no ~/.opensquilla: the cli-home probe finds nothing
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
desktop = _make_home(tmp_path / "desktop-home")
monkeypatch.setattr(opensquilla_home, "detect_desktop_home", lambda: desktop)
base = tmp_path / "appdata-local"
_make_home(base / "OpenSquilla" / "portable" / "dummy-release")
monkeypatch.setenv("LOCALAPPDATA", str(base))
monkeypatch.delenv("TEMP", raising=False)
candidate = detect_legacy_home(tmp_path / "target-home")
assert candidate == LegacyHomeCandidate(path=desktop, kind="desktop-home")
def test_desktop_home_matching_target_is_not_offered_but_old_marker_does_not_hide_it(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
_no_portable_bases: None,
) -> None:
fake_home = tmp_path / "userhome"
fake_home.mkdir()
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
desktop = _make_home(tmp_path / "desktop-home")
desktop_probes: list[Path] = []
monkeypatch.setattr(
opensquilla_home,
"detect_desktop_home",
lambda: desktop_probes.append(desktop) or desktop,
)
assert detect_legacy_home(desktop) is None
assert desktop_probes == [desktop]
marker_probes: list[tuple[Path, Path]] = []
monkeypatch.setattr(
opensquilla_home,
"_source_marker_matches_target",
lambda source, target: marker_probes.append((source, target)) or source == desktop,
)
target = tmp_path / "target-home"
assert detect_legacy_home(target) == LegacyHomeCandidate(
path=desktop,
kind="desktop-home",
)
assert marker_probes == []
def test_portable_fallback_offers_newest_candidate(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
fake_home = tmp_path / "userhome"
fake_home.mkdir() # no ~/.opensquilla: the cli-home probe finds nothing
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
base = tmp_path / "appdata-local"
older = _make_home(base / "OpenSquilla" / "portable" / "dummy-release-a")
newer = _make_home(base / "OpenSquilla" / "portable" / "dummy-release-b")
now = time.time()
os.utime(older / "config.toml", (now - 1000, now - 1000))
os.utime(newer / "config.toml", (now, now))
monkeypatch.setenv("LOCALAPPDATA", str(base))
monkeypatch.delenv("TEMP", raising=False)
candidate = detect_legacy_home(tmp_path / "target-home")
assert candidate == LegacyHomeCandidate(path=newer, kind="windows-portable")
def test_portable_fallback_keeps_distinct_paths_when_identity_is_unavailable(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
fake_home = tmp_path / "userhome"
fake_home.mkdir()
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
base = tmp_path / "appdata-local"
older = _make_home(base / "OpenSquilla" / "portable" / "dummy-release-a")
newer = _make_home(base / "OpenSquilla" / "portable" / "dummy-release-b")
now = time.time()
os.utime(older / "config.toml", (now - 1000, now - 1000))
os.utime(newer / "config.toml", (now, now))
monkeypatch.setenv("LOCALAPPDATA", str(base))
monkeypatch.delenv("TEMP", raising=False)
monkeypatch.setattr(opensquilla_home, "_advisory_identity", lambda _result: None)
candidate = detect_legacy_home(tmp_path / "target-home")
assert candidate == LegacyHomeCandidate(path=newer, kind="windows-portable")
def test_portable_candidate_that_is_the_target_is_skipped(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
fake_home = tmp_path / "userhome"
fake_home.mkdir()
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
base = tmp_path / "appdata-local"
live = _make_home(base / "OpenSquilla" / "portable" / "dummy-release-a")
monkeypatch.setenv("LOCALAPPDATA", str(base))
monkeypatch.delenv("TEMP", raising=False)
assert detect_legacy_home(live) is None
def test_old_portable_marker_does_not_hide_candidates(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
fake_home = tmp_path / "userhome"
fake_home.mkdir()
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
base = tmp_path / "appdata-local"
imported = _make_home(base / "OpenSquilla" / "portable" / "dummy-release-a")
monkeypatch.setenv("LOCALAPPDATA", str(base))
monkeypatch.delenv("TEMP", raising=False)
monkeypatch.setattr(
opensquilla_home,
"_source_marker_matches_target",
lambda source, _target: source == imported,
)
candidate = detect_legacy_home(tmp_path / "target-home")
# Receipt/marker state is a display hint only. Candidate discovery must not
# silently hide data that the user may explicitly choose to import again.
assert candidate == LegacyHomeCandidate(path=imported, kind="windows-portable")
@pytest.mark.skipif(sys.platform == "win32", reason="POSIX-only guard")
def test_portable_enumeration_is_skipped_without_base_env(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
_no_portable_bases: None,
) -> None:
fake_home = tmp_path / "userhome"
fake_home.mkdir()
monkeypatch.setattr(Path, "home", classmethod(lambda cls: fake_home))
calls: list[object] = []
monkeypatch.setattr(
opensquilla_home,
"enumerate_portable_homes",
lambda bases=None: calls.append(bases) or [],
)
assert detect_legacy_home(tmp_path / "target-home") is None
assert not calls
def test_detection_swallows_os_errors(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
def _explode(target: Path) -> Path | None:
raise OSError("disk unreadable")
monkeypatch.setattr(opensquilla_home, "detect_legacy_cli_home", _explode)
assert detect_legacy_home(tmp_path / "target-home") is None
def test_suggested_migrate_command_renders_kind_and_source() -> None:
source = Path(os.sep) / "legacy" / "home"
candidate = LegacyHomeCandidate(path=source, kind="cli-home")
rendered_source = f"'{source}'" if os.name == "nt" else str(source)
assert suggested_migrate_command(candidate) == (
f"opensquilla migrate opensquilla --kind cli-home --source {rendered_source}"
)
def test_suggested_migrate_command_quotes_paths_with_spaces() -> None:
source = Path(os.sep) / "legacy homes" / "data dir"
candidate = LegacyHomeCandidate(path=source, kind="windows-portable")
assert suggested_migrate_command(candidate) == (
"opensquilla migrate opensquilla --kind windows-portable "
f"--source '{source}'"
)
def test_suggested_migrate_command_quotes_posix_shell_metacharacters(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.onboarding import next_steps
monkeypatch.setattr(next_steps.platform, "system", lambda: "Linux")
source = Path(os.sep) / "legacy$HOME"
candidate = LegacyHomeCandidate(path=source, kind="cli-home")
assert suggested_migrate_command(candidate).endswith(f"--source '{source}'")
def test_suggested_migrate_command_uses_powershell_quoting(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.onboarding import next_steps
monkeypatch.setattr(next_steps.platform, "system", lambda: "Windows")
source = Path("C:\\O'Brien Data\\profile")
candidate = LegacyHomeCandidate(path=source, kind="windows-portable")
command = suggested_migrate_command(candidate)
assert command.endswith("--source 'C:\\O''Brien Data\\profile'")
assert "'\"'\"'" not in command