""" Integration tests for install.py and uninstall.py — cross-platform. Replaces tests/test_install.bats. Each test runs inside a fully isolated tmp_path with a fake inkscape binary injected via PATH and an overridden config home pointing to tmp_path. """ from __future__ import annotations import os import platform import subprocess import sys from pathlib import Path import pytest REPO = Path(__file__).parent.parent INSTALL_PY = REPO / "install.py" UNINSTALL_PY = REPO / "uninstall.py" _FAKE_INKSCAPE_SH = """\ #!/usr/bin/env sh case "$1" in --version) echo 'Inkscape 1.3.2 (1:1.3.2+202311252150+091e20ef0f)' ;; esac """ _FAKE_INKSCAPE_BAT = """\ @echo off if "%1"=="--version" echo Inkscape 1.3.2 (1:1.3.2+202311252150+091e20ef0f) """ def _make_fake_inkscape(bin_dir: Path) -> None: """Create a platform-appropriate fake inkscape binary in *bin_dir*.""" if platform.system() == "Windows": bat = bin_dir / "inkscape.bat" bat.write_text(_FAKE_INKSCAPE_BAT) else: script = bin_dir / "inkscape" script.write_text(_FAKE_INKSCAPE_SH) script.chmod(0o755) def _config_dir(tmp_path: Path) -> Path: """ Return the expected config path matching detect_config() for this platform when the env fixture overrides the config home to *tmp_path*. """ if platform.system() == "Windows": # install.py on Windows: APPDATA/inkscape return tmp_path / "config" / "inkscape" else: # install.py on Linux/macOS: XDG_CONFIG_HOME/inkscape return tmp_path / "config" / "inkscape" @pytest.fixture() def env(tmp_path: Path): """ Isolated subprocess environment with a fake inkscape and a temp config dir. On Linux/macOS: overrides HOME and XDG_CONFIG_HOME. On Windows: overrides APPDATA and LOCALAPPDATA. """ bin_dir = tmp_path / "bin" bin_dir.mkdir() _make_fake_inkscape(bin_dir) cfg_dir = _config_dir(tmp_path) cfg_dir.mkdir(parents=True, exist_ok=True) base_env = os.environ.copy() base_env["PATH"] = str(bin_dir) + os.pathsep + base_env.get("PATH", "") # Block flatpak / snap detection inside subprocesses base_env.pop("FLATPAK_ID", None) if platform.system() == "Windows": base_env["APPDATA"] = str(tmp_path / "config") base_env["LOCALAPPDATA"] = str(tmp_path / "local") else: base_env["XDG_CONFIG_HOME"] = str(tmp_path / "config") base_env["HOME"] = str(tmp_path) return { "tmp_path": tmp_path, "config_dir": cfg_dir, "env": base_env, } def _run_install(env_fixture: dict, preset: str = "cc") -> subprocess.CompletedProcess[str]: return subprocess.run( [sys.executable, str(INSTALL_PY), "--yes", f"--preset={preset}"], env=env_fixture["env"], cwd=str(REPO), capture_output=True, text=True, ) def _run_uninstall(env_fixture: dict) -> subprocess.CompletedProcess[str]: return subprocess.run( [sys.executable, str(UNINSTALL_PY)], env=env_fixture["env"], cwd=str(REPO), capture_output=True, text=True, ) # ── install tests ───────────────────────────────────────────────────────────── def test_install_creates_backup(env): result = _run_install(env) assert result.returncode == 0, result.stderr backups = list(env["tmp_path"].glob("config/inkscape.bak-illuscape-*")) assert backups, "No backup directory created" def test_install_backup_is_idempotent(env): _run_install(env) first_backups = sorted(env["tmp_path"].glob("config/inkscape.bak-illuscape-*")) _run_install(env) second_backups = sorted(env["tmp_path"].glob("config/inkscape.bak-illuscape-*")) assert first_backups == second_backups, "Second install created an extra backup" def test_install_cc_key_file(env): result = _run_install(env, preset="cc") assert result.returncode == 0, result.stderr assert (env["config_dir"] / "keys/illustrator-cc.xml").exists() def test_install_cs6_key_file(env): result = _run_install(env, preset="cs6") assert result.returncode == 0, result.stderr assert (env["config_dir"] / "keys/illustrator-cs6.xml").exists() def test_install_palettes(env): result = _run_install(env) assert result.returncode == 0, result.stderr for name in ( "Illustrator-Defaults.gpl", "Illustrator-Grays.gpl", "Illustrator-Earth.gpl", ): assert (env["config_dir"] / "palettes" / name).exists(), f"Missing palette: {name}" def test_install_templates(env): result = _run_install(env) assert result.returncode == 0, result.stderr for name in ( "Letter.svg", "A4.svg", "Web-1920x1080.svg", "Web-1280x720.svg", "Print-CMYK-Letter.svg", "Print-CMYK-A4.svg", ): assert (env["config_dir"] / "templates" / name).exists(), f"Missing template: {name}" def test_install_creates_prefs(env): result = _run_install(env) assert result.returncode == 0, result.stderr assert (env["config_dir"] / "preferences.xml").exists() def test_install_prefs_contains_units(env): result = _run_install(env) assert result.returncode == 0, result.stderr content = (env["config_dir"] / "preferences.xml").read_text() assert "px" in content or "pt" in content, "No unit setting found in preferences.xml" def test_install_noninteractive(env): """--yes + --preset should exit 0 without any stdin.""" result = _run_install(env) assert result.returncode == 0, f"Non-interactive install failed:\n{result.stderr}" # ── uninstall tests ─────────────────────────────────────────────────────────── def test_uninstall_restores_prefs(env): """Original prefs.xml is restored after install + uninstall.""" original_xml = '' (env["config_dir"] / "preferences.xml").write_text(original_xml) _run_install(env) # prefs should now contain the patched version result = _run_uninstall(env) assert result.returncode == 0, result.stderr restored = (env["config_dir"] / "preferences.xml").read_text() assert restored == original_xml, "prefs.xml was not restored to the original content" def test_uninstall_removes_palettes(env): _run_install(env) result = _run_uninstall(env) assert result.returncode == 0, result.stderr for name in ( "Illustrator-Defaults.gpl", "Illustrator-Grays.gpl", "Illustrator-Earth.gpl", ): assert not ( env["config_dir"] / "palettes" / name ).exists(), f"Palette was not removed: {name}" def test_uninstall_removes_templates(env): _run_install(env) result = _run_uninstall(env) assert result.returncode == 0, result.stderr for name in ("Letter.svg", "A4.svg"): assert not ( env["config_dir"] / "templates" / name ).exists(), f"Template was not removed: {name}"