CI / quality (push) Canceled after 0s
Adds claim-bound online evidence records and citation verification scripts, node removal transport/adapter, retrieval benchmark and owner-review tooling, GraphCanvas/SourceDetail knowledge browser UI, and the ingest handoff helper. Bumps the agents/gewu pack to the online-verification branch.
218 lines
8.8 KiB
Python
218 lines
8.8 KiB
Python
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"
|