Files
GEWU/tests/test_pack_installer.py
T

217 lines
8.8 KiB
Python
Raw Normal View History

from __future__ import annotations
import importlib.util
import json
from pathlib import Path
import pytest
INSTALLER_PATH = Path(__file__).resolve().parents[1] / "agents/gewu/scripts/install.py"
SPEC = importlib.util.spec_from_file_location("gewu_pack_installer", INSTALLER_PATH)
assert SPEC is not None and SPEC.loader is not None
installer = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(installer)
def make_pack(tmp_path: Path) -> Path:
pack = tmp_path / "pack"
(pack / "scripts").mkdir(parents=True)
(pack / "manifest.json").write_text(
json.dumps(
{
"pack_id": "fixture",
"pack_version": "1.0.0",
"snapshot": {"digest": "a" * 64},
"modules": [
{
"module_id": "profile",
"implementation_version": "1.0.0",
"sha256": installer.digest(b"fixture profile\n"),
}
],
}
)
+ "\n",
encoding="utf-8",
)
(pack / "AGENTS.md").write_text("fixture profile\n", encoding="utf-8")
return pack
def test_install_and_uninstall_preserve_unrelated_workspace_and_mcp_config(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
(workspace / "AGENTS.md").write_bytes(b"Existing project guidance\nKeep it.\n")
(workspace / "CLAUDE.md").write_bytes(b"Existing Claude instructions without final newline")
(workspace / ".gemini").mkdir()
mcp_config = workspace / ".gemini/settings.json"
original_mcp = b'{"mcpServers":{"other":{"httpUrl":"https://example.test/mcp"}}}\n'
mcp_config.write_bytes(original_mcp)
(workspace / ".agents").mkdir()
(workspace / ".agents/skills").mkdir()
(workspace / ".agents/skills/custom.md").write_text("user skill", encoding="utf-8")
original_agents = (workspace / "AGENTS.md").read_bytes()
original_claude = (workspace / "CLAUDE.md").read_bytes()
installer.install(workspace, make_pack(tmp_path), "integrated")
assert (workspace / ".agents/gewu/AGENTS.md").is_file()
assert original_agents in (workspace / "AGENTS.md").read_bytes()
assert original_claude in (workspace / "CLAUDE.md").read_bytes()
assert (workspace / "GEMINI.md").is_file()
assert mcp_config.read_bytes() == original_mcp
assert (workspace / ".agents/skills/custom.md").read_text() == "user skill"
installer.uninstall(workspace)
assert (workspace / "AGENTS.md").read_bytes() == original_agents
assert (workspace / "CLAUDE.md").read_bytes() == original_claude
assert not (workspace / "GEMINI.md").exists()
assert mcp_config.read_bytes() == original_mcp
assert (workspace / ".agents/skills/custom.md").read_text() == "user skill"
assert not (workspace / ".agents/gewu").exists()
assert not (workspace / ".agents/gewu-install.json").exists()
def test_uninstall_refuses_modified_owned_entry_and_preserves_everything(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
(workspace / "AGENTS.md").write_text("Project rules\n", encoding="utf-8")
installer.install(workspace, make_pack(tmp_path))
(workspace / "AGENTS.md").write_text("User edit after installation\n", encoding="utf-8")
with pytest.raises(ValueError, match="edited routing block"):
installer.uninstall(workspace)
assert "User edit after installation" in (workspace / "AGENTS.md").read_text()
assert (workspace / ".agents/gewu/manifest.json").is_file()
assert (workspace / ".agents/gewu-install.json").is_file()
def test_uninstall_preserves_edits_outside_routing_block_and_user_pack_files(
tmp_path: Path,
) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
root_instructions = workspace / "AGENTS.md"
root_instructions.write_text("Project rules\n", encoding="utf-8")
installer.install(workspace, make_pack(tmp_path))
root_instructions.write_text(
"Updated project rules\n" + root_instructions.read_text(encoding="utf-8"),
encoding="utf-8",
)
custom = workspace / ".agents/gewu/user-notes.txt"
custom.write_text("user-owned", encoding="utf-8")
installer.uninstall(workspace)
assert "Updated project rules" in root_instructions.read_text(encoding="utf-8")
assert custom.read_text(encoding="utf-8") == "user-owned"
assert (workspace / ".agents/gewu").is_dir()
assert not (workspace / ".agents/gewu/AGENTS.md").exists()
def test_install_refuses_occupied_gewu_destination_without_overwriting(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
(workspace / ".agents/gewu").mkdir(parents=True)
marker = workspace / ".agents/gewu/keep.txt"
marker.write_text("user data", encoding="utf-8")
with pytest.raises(FileExistsError):
installer.install(workspace, make_pack(tmp_path))
assert marker.read_text() == "user data"
def test_install_rollback_restores_original_routes_if_pack_copy_fails(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
original = b"Existing instructions\n"
(workspace / "AGENTS.md").write_bytes(original)
pack = make_pack(tmp_path)
real_atomic_write = installer._atomic_write
def failing_write(path: Path, content: bytes) -> None:
if path.name == "CLAUDE.md":
raise OSError("fixture failure")
real_atomic_write(path, content)
monkeypatch.setattr(installer, "_atomic_write", failing_write)
with pytest.raises(OSError, match="fixture failure"):
installer.install(workspace, pack)
assert (workspace / "AGENTS.md").read_bytes() == original
assert not (workspace / "CLAUDE.md").exists()
assert not (workspace / "GEMINI.md").exists()
assert not (workspace / ".agents/gewu").exists()
assert not (workspace / ".agents/gewu-install.json").exists()
def test_racing_installer_does_not_delete_the_other_installation(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
pack = make_pack(tmp_path)
original_mkdir = Path.mkdir
def competing_create(
path: Path,
mode: int = 0o777,
parents: bool = False,
exist_ok: bool = False,
) -> None:
if path == workspace / ".agents/gewu":
original_mkdir(path, mode=mode, parents=parents, exist_ok=True)
(path / "other-installation.txt").write_text("keep", encoding="utf-8")
raise FileExistsError("another installer won the race")
original_mkdir(path, mode=mode, parents=parents, exist_ok=exist_ok)
monkeypatch.setattr(Path, "mkdir", competing_create)
with pytest.raises(FileExistsError, match="another installer"):
installer.install(workspace, pack)
assert (workspace / ".agents/gewu/other-installation.txt").read_text() == "keep"
assert not (workspace / "AGENTS.md").exists()
def test_uninstall_rejects_traversal_in_edited_state_before_deleting_files(
tmp_path: Path,
) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
outside = workspace.parent / "keep.txt"
outside.write_text("user data", encoding="utf-8")
installer.install(workspace, make_pack(tmp_path))
state_path = workspace / ".agents/gewu-install.json"
state = json.loads(state_path.read_text(encoding="utf-8"))
state["files"]["../../keep.txt"] = installer.digest(b"user data")
state_path.write_text(json.dumps(state), encoding="utf-8")
with pytest.raises(ValueError, match="file list is invalid"):
installer.uninstall(workspace)
assert outside.read_text(encoding="utf-8") == "user data"
assert (workspace / ".agents/gewu/manifest.json").is_file()
def test_uninstall_rejects_unrecognized_route_names_in_edited_state(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
workspace.mkdir()
installer.install(workspace, make_pack(tmp_path))
state_path = workspace / ".agents/gewu-install.json"
state = json.loads(state_path.read_text(encoding="utf-8"))
state["routes"]["../../keep.txt"] = state["routes"].pop("AGENTS.md")
state_path.write_text(json.dumps(state), encoding="utf-8")
with pytest.raises(ValueError, match="routes are invalid"):
installer.uninstall(workspace)
assert (workspace / "AGENTS.md").is_file()
def test_uninstall_rejects_agents_symlink_before_reading_state(tmp_path: Path) -> None:
workspace = tmp_path / "workspace"
external = tmp_path / "external"
workspace.mkdir()
external.mkdir()
(external / "gewu-install.json").write_text("not a state file", encoding="utf-8")
(workspace / ".agents").symlink_to(external, target_is_directory=True)
with pytest.raises(ValueError, match=".agents must not be a symlink"):
installer.uninstall(workspace)
assert (external / "gewu-install.json").read_text(encoding="utf-8") == "not a state file"