Rebuilt from the standalone 54e65eb tree (durable transactions, Kiro IDE adapter, hook retry with a bounded chain lock, version 2.0.2) with the Open-Source API trims re-applied: no token rotation, token issuance, or agent integration updates, and the mirror's registration contract and OpenCode plugin tests kept. Claude-Session: https://claude.ai/code/session_01QoUjdc7HGKTsgj5GhG8HvF
317 lines
9.6 KiB
Python
317 lines
9.6 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess # nosec B404
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
from cline_support import (
|
|
AGENT_ID,
|
|
VALID_PRIVATE_KEY,
|
|
assert_no_transaction_files,
|
|
assert_snapshot,
|
|
prepare_fixture,
|
|
snapshot_installation,
|
|
symlink_or_skip,
|
|
write_json,
|
|
write_text,
|
|
)
|
|
from elydora.plugins import _transaction, cline_installation
|
|
from elydora.plugins.cline_contract import resolve_hook_files
|
|
from elydora.plugins.cline_io import read_hook_file
|
|
|
|
|
|
def prepare_installation(fixture: object) -> list[_transaction.FileChange]:
|
|
paths = resolve_hook_files()
|
|
guard = read_hook_file(paths.guard_path)
|
|
audit = read_hook_file(paths.audit_path)
|
|
return cline_installation.prepare_cline_installation(
|
|
fixture.config, guard, audit
|
|
)
|
|
|
|
|
|
def test_transaction_rolls_back_all_six_files(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
real_replace = _transaction._replace_physical
|
|
failed = False
|
|
|
|
def fail_audit_once(
|
|
source: str,
|
|
destination: str,
|
|
directory: _transaction.PinnedDirectory,
|
|
) -> None:
|
|
nonlocal failed
|
|
if os.path.abspath(destination) == str(fixture.audit_wrapper) and not failed:
|
|
failed = True
|
|
raise OSError("injected Cline audit hook failure")
|
|
real_replace(source, destination, directory)
|
|
|
|
monkeypatch.setattr(_transaction, "_replace_physical", fail_audit_once)
|
|
with pytest.raises(OSError, match="injected Cline audit hook failure"):
|
|
fixture.install()
|
|
|
|
assert failed is True
|
|
assert all(not path.exists() for path in fixture.managed_paths())
|
|
assert_no_transaction_files(fixture.home_dir)
|
|
assert_no_transaction_files(fixture.cline_dir)
|
|
|
|
|
|
def test_concurrent_hook_replacement_is_detected_before_final_commit(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
changes = prepare_installation(fixture)
|
|
concurrent = "// concurrently replaced\n"
|
|
real_replace = _transaction._replace_physical
|
|
injected = False
|
|
|
|
def inject_replacement(
|
|
source: str,
|
|
destination: str,
|
|
directory: _transaction.PinnedDirectory,
|
|
) -> None:
|
|
nonlocal injected
|
|
if not injected:
|
|
injected = True
|
|
write_text(fixture.audit_wrapper, concurrent, 0o700)
|
|
real_replace(source, destination, directory)
|
|
|
|
monkeypatch.setattr(_transaction, "_replace_physical", inject_replacement)
|
|
with pytest.raises(OSError, match="changed during installation"):
|
|
cline_installation.commit_cline_installation(changes)
|
|
|
|
assert injected is True
|
|
assert fixture.audit_wrapper.read_text(encoding="utf-8") == concurrent
|
|
for path in fixture.managed_paths()[:-1]:
|
|
assert not path.exists()
|
|
assert_no_transaction_files(fixture.home_dir)
|
|
assert_no_transaction_files(fixture.cline_dir)
|
|
|
|
|
|
def test_stale_hook_snapshot_is_rejected_before_staging(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
paths = resolve_hook_files()
|
|
guard = read_hook_file(paths.guard_path)
|
|
audit = read_hook_file(paths.audit_path)
|
|
concurrent = "// stale snapshot replacement\n"
|
|
write_text(fixture.audit_wrapper, concurrent, 0o700)
|
|
|
|
with pytest.raises(OSError, match="changed before staging"):
|
|
cline_installation.prepare_cline_installation(
|
|
fixture.config, guard, audit
|
|
)
|
|
|
|
assert fixture.audit_wrapper.read_text(encoding="utf-8") == concurrent
|
|
assert not fixture.agent_dir.exists()
|
|
assert_no_transaction_files(fixture.root_dir)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"runtime_name",
|
|
[
|
|
"private.key",
|
|
"guard.py",
|
|
"hook.py",
|
|
"chain-state.json",
|
|
"status-cache.json",
|
|
"error.log",
|
|
],
|
|
)
|
|
def test_orphan_runtime_artifacts_fail_before_hook_writes(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
runtime_name: str,
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
artifact = fixture.agent_dir / runtime_name
|
|
write_text(artifact, "orphan\n")
|
|
|
|
with pytest.raises(ValueError, match="identity cannot be verified"):
|
|
fixture.install()
|
|
|
|
assert artifact.read_text(encoding="utf-8") == "orphan\n"
|
|
assert not fixture.guard_wrapper.exists()
|
|
assert not fixture.audit_wrapper.exists()
|
|
|
|
|
|
def test_mismatched_runtime_identity_fails_before_writes(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
write_json(
|
|
fixture.runtime_config,
|
|
{"agent_id": "another-agent", "agent_name": "cline"},
|
|
)
|
|
original = fixture.runtime_config.read_text(encoding="utf-8")
|
|
|
|
with pytest.raises(ValueError, match="identity does not match"):
|
|
fixture.install()
|
|
|
|
assert fixture.runtime_config.read_text(encoding="utf-8") == original
|
|
assert not fixture.guard_wrapper.exists()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"kind", ["configuration", "hooks", "runtime", "hook"]
|
|
)
|
|
def test_linked_configuration_runtime_and_hook_paths_are_rejected(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
kind: str,
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
target = tmp_path / f"{kind}-target"
|
|
if kind == "configuration":
|
|
target.mkdir()
|
|
symlink_or_skip(target, fixture.cline_dir, directory=True)
|
|
elif kind == "hooks":
|
|
target.mkdir()
|
|
fixture.cline_dir.mkdir()
|
|
symlink_or_skip(target, fixture.hooks_dir, directory=True)
|
|
elif kind == "runtime":
|
|
target.mkdir()
|
|
fixture.home_dir.mkdir()
|
|
symlink_or_skip(
|
|
target, fixture.home_dir / ".elydora", directory=True
|
|
)
|
|
else:
|
|
fixture.install()
|
|
write_text(target, "external hook\n")
|
|
fixture.guard_wrapper.unlink()
|
|
symlink_or_skip(target, fixture.guard_wrapper)
|
|
|
|
with pytest.raises(OSError, match="physical (file|directory)"):
|
|
fixture.install()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("field", "value", "pattern"),
|
|
[
|
|
("agent_name", "codex", "requires agent_name cline"),
|
|
("agent_id", "../escape", "Invalid agent ID"),
|
|
("private_key", "invalid", "canonical 32-byte"),
|
|
("token", "", "non-empty string"),
|
|
("base_url", "https://api.elydora.com?q=1", "query parameters"),
|
|
("guard_script_path", "outside", "managed agent directory"),
|
|
],
|
|
)
|
|
def test_install_validates_runtime_inputs_before_writes(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
field: str,
|
|
value: str,
|
|
pattern: str,
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
fixture.config[field] = value
|
|
|
|
with pytest.raises(ValueError, match=pattern):
|
|
fixture.install()
|
|
|
|
assert not fixture.agent_dir.exists()
|
|
assert not fixture.hooks_dir.exists()
|
|
|
|
|
|
def test_uninstall_restores_both_hooks_when_second_removal_fails(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
fixture.install()
|
|
before = snapshot_installation(fixture)
|
|
paths = resolve_hook_files()
|
|
changes = cline_installation.prepare_cline_uninstall(
|
|
(
|
|
read_hook_file(paths.guard_path),
|
|
read_hook_file(paths.audit_path),
|
|
),
|
|
AGENT_ID,
|
|
)
|
|
real_capture = _transaction._capture_physical
|
|
failed = False
|
|
|
|
def fail_audit_removal(
|
|
source: str,
|
|
destination: str,
|
|
directory: _transaction.PinnedDirectory,
|
|
) -> None:
|
|
nonlocal failed
|
|
if os.path.abspath(source) == str(fixture.audit_wrapper) and not failed:
|
|
failed = True
|
|
raise OSError("injected Cline uninstall failure")
|
|
real_capture(source, destination, directory)
|
|
|
|
monkeypatch.setattr(_transaction, "_capture_physical", fail_audit_removal)
|
|
with pytest.raises(OSError, match="injected Cline uninstall failure"):
|
|
cline_installation.commit_cline_uninstall(changes)
|
|
|
|
assert failed is True
|
|
assert_snapshot(before)
|
|
assert_no_transaction_files(fixture.root_dir)
|
|
|
|
|
|
def test_installation_leaves_no_transaction_artifacts(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
fixture.install()
|
|
assert_no_transaction_files(fixture.root_dir)
|
|
|
|
|
|
def test_cli_preflight_preserves_a_hook_collision(tmp_path: Path) -> None:
|
|
home_dir = tmp_path / "cli-home"
|
|
hook_path = home_dir / ".cline" / "hooks" / "PreToolUse.mjs"
|
|
key_path = tmp_path / "private.key"
|
|
write_text(hook_path, "// user hook\n", 0o700)
|
|
write_text(key_path, VALID_PRIVATE_KEY)
|
|
environment = {
|
|
key: value
|
|
for key, value in {
|
|
**os.environ,
|
|
"HOME": str(home_dir),
|
|
"USERPROFILE": str(home_dir),
|
|
}.items()
|
|
if key != "CLINE_DIR"
|
|
}
|
|
result = subprocess.run( # nosec B603
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"elydora.cli",
|
|
"install",
|
|
"--agent",
|
|
"cline",
|
|
"--org_id",
|
|
"org-1",
|
|
"--agent_id",
|
|
AGENT_ID,
|
|
"--private_key_file",
|
|
str(key_path),
|
|
"--kid",
|
|
"key-1",
|
|
"--base_url",
|
|
"https://api.elydora.com",
|
|
],
|
|
capture_output=True,
|
|
check=False,
|
|
cwd=Path(__file__).parents[1],
|
|
env=environment,
|
|
text=True,
|
|
)
|
|
|
|
assert result.returncode == 1
|
|
assert "owned by another integration" in result.stderr
|
|
assert hook_path.read_text(encoding="utf-8") == "// user hook\n"
|
|
assert not (home_dir / ".elydora").exists()
|