112 lines
3.8 KiB
Python
112 lines
3.8 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from elydora.plugins import _transaction
|
|
from elydora.plugins.letta_config import render_letta_document
|
|
from elydora.plugins.letta_contract import build_letta_group
|
|
from elydora.plugins.letta_installation import (
|
|
commit_letta_installation,
|
|
preflight_letta_installation,
|
|
prepare_letta_installation,
|
|
)
|
|
from elydora.plugins.letta_sources import read_letta_sources
|
|
from letta_support import prepare_fixture, write_json
|
|
|
|
|
|
def _groups(guard_path: str, audit_path: str) -> dict:
|
|
return {
|
|
"PreToolUse": build_letta_group(guard_path),
|
|
"PostToolUse": build_letta_group(audit_path),
|
|
"PostToolUseFailure": build_letta_group(audit_path),
|
|
}
|
|
|
|
|
|
def test_transaction_aborts_when_read_only_source_changes(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
fixture = prepare_fixture(
|
|
monkeypatch, tmp_path, project_settings={"owner": "before"}
|
|
)
|
|
sources = read_letta_sources()
|
|
paths = preflight_letta_installation(fixture.config, sources)
|
|
rendered = render_letta_document(
|
|
sources.global_settings,
|
|
None,
|
|
_groups(paths.guard_path, paths.audit_path),
|
|
)
|
|
changes = prepare_letta_installation(fixture.config, paths, rendered)
|
|
write_json(fixture.project_path, {"owner": "after"})
|
|
with pytest.raises(OSError, match="project settings changed"):
|
|
commit_letta_installation(changes, sources)
|
|
assert json.loads(fixture.project_path.read_text()) == {"owner": "after"}
|
|
assert not fixture.global_path.exists()
|
|
assert not fixture.agent_dir.exists()
|
|
|
|
|
|
def test_transaction_rolls_back_every_runtime_and_settings_change(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
fixture = prepare_fixture(
|
|
monkeypatch, tmp_path, global_settings={"owner": "user"}
|
|
)
|
|
original = fixture.source()
|
|
real_replace = _transaction.os.replace
|
|
failed = False
|
|
|
|
def fail_settings_commit(source: Any, destination: Any) -> None:
|
|
nonlocal failed
|
|
if not failed and Path(destination) == fixture.global_path:
|
|
failed = True
|
|
raise OSError("simulated Letta settings commit failure")
|
|
real_replace(source, destination)
|
|
|
|
monkeypatch.setattr(_transaction.os, "replace", fail_settings_commit)
|
|
with pytest.raises(OSError, match="Install Letta Code hooks"):
|
|
fixture.install()
|
|
assert failed
|
|
assert fixture.source() == original
|
|
for file_path in (
|
|
fixture.guard_path,
|
|
fixture.audit_path,
|
|
fixture.runtime_config_path,
|
|
fixture.private_key_path,
|
|
):
|
|
assert not file_path.exists()
|
|
assert [
|
|
path
|
|
for path in fixture.home_dir.rglob("*")
|
|
if path.suffix in {".tmp", ".rollback"}
|
|
] == []
|
|
|
|
|
|
def test_install_rejects_linked_configuration_directory(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
redirected = tmp_path / "redirected-letta"
|
|
redirected.mkdir()
|
|
fixture.home_dir.mkdir(parents=True)
|
|
try:
|
|
os.symlink(redirected, fixture.global_path.parent, target_is_directory=True)
|
|
except (NotImplementedError, OSError):
|
|
pytest.skip("directory links require platform privileges")
|
|
with pytest.raises(OSError, match="global configuration directory"):
|
|
fixture.install()
|
|
assert not fixture.agent_dir.exists()
|
|
|
|
|
|
def test_install_rejects_directory_at_settings_path(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
) -> None:
|
|
fixture = prepare_fixture(monkeypatch, tmp_path)
|
|
fixture.global_path.mkdir(parents=True)
|
|
with pytest.raises(OSError, match="global settings path"):
|
|
fixture.install()
|
|
assert not fixture.agent_dir.exists()
|