170 lines
6.7 KiB
Python
170 lines
6.7 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import importlib.util
|
|
import io
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from urllib.error import URLError
|
|
|
|
import pytest
|
|
|
|
CORE = Path(__file__).resolve().parents[1]
|
|
SPEC = importlib.util.spec_from_file_location("handoff_launcher", CORE / "python" / "session_handoff.py")
|
|
assert SPEC and SPEC.loader
|
|
launcher = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(launcher)
|
|
REVISION = "1" * 40
|
|
FILES = {
|
|
"handoff_engine.py": b"def main(argv=None, default_source=None):\n return 0\n",
|
|
"handoff_sources.py": b"# reader\n",
|
|
}
|
|
|
|
|
|
def installation(tmp_path, monkeypatch):
|
|
plugin = tmp_path / "plugin"
|
|
plugin.mkdir()
|
|
manifest = {"revision": REVISION, "files": {name: hashlib.sha256(body).hexdigest() for name, body in FILES.items()}}
|
|
(plugin / "handoff-runtime.json").write_text(json.dumps(manifest))
|
|
monkeypatch.setattr(launcher.Path, "home", lambda: tmp_path)
|
|
requested = []
|
|
|
|
def download(url, timeout):
|
|
requested.append(url)
|
|
assert timeout == 30
|
|
return io.BytesIO(FILES[url.rsplit("/", 1)[-1]])
|
|
|
|
monkeypatch.setattr(launcher, "urlopen", download)
|
|
return plugin, tmp_path / ".mem0" / "handoff-runtime" / REVISION, requested
|
|
|
|
|
|
def test_standalone_download_is_pinned_then_works_offline(tmp_path, monkeypatch):
|
|
plugin, cache, requested = installation(tmp_path, monkeypatch)
|
|
assert launcher.runtime_root(plugin) == cache
|
|
assert set(requested) == {
|
|
f"https://raw.githubusercontent.com/mem0ai/mem0/{REVISION}/integrations/agent-plugin-core/python/{name}"
|
|
for name in FILES
|
|
}
|
|
assert {name: (cache / name).read_bytes() for name in FILES} == FILES
|
|
|
|
def offline(*args, **kwargs):
|
|
raise AssertionError("verified cached runtime must not access the network")
|
|
|
|
monkeypatch.setattr(launcher, "urlopen", offline)
|
|
assert launcher.runtime_root(plugin) == cache
|
|
|
|
|
|
def test_canonical_checkout_needs_no_manifest_cache_or_network(tmp_path, monkeypatch):
|
|
for name, body in FILES.items():
|
|
(tmp_path / name).write_bytes(body)
|
|
monkeypatch.setattr(launcher, "urlopen", lambda *args, **kwargs: pytest.fail("unexpected network"))
|
|
assert launcher.runtime_root(tmp_path) == tmp_path
|
|
assert not (tmp_path / ".mem0").exists()
|
|
|
|
|
|
@pytest.mark.parametrize("damage", ["tampered", "missing"])
|
|
def test_every_use_detects_and_repairs_damaged_cache(tmp_path, monkeypatch, damage):
|
|
plugin, cache, requested = installation(tmp_path, monkeypatch)
|
|
launcher.runtime_root(plugin)
|
|
target = cache / "handoff_sources.py"
|
|
if damage == "tampered":
|
|
target.write_bytes(b"untrusted code")
|
|
else:
|
|
target.unlink()
|
|
requested.clear()
|
|
assert launcher.runtime_root(plugin) == cache
|
|
assert target.read_bytes() == FILES[target.name]
|
|
assert len(requested) == 1
|
|
assert requested[0].endswith("/handoff_sources.py")
|
|
|
|
|
|
def test_failed_second_download_publishes_no_partial_runtime(tmp_path, monkeypatch):
|
|
plugin, cache, _ = installation(tmp_path, monkeypatch)
|
|
|
|
def download(url, timeout):
|
|
if url.endswith("handoff_sources.py"):
|
|
raise URLError("offline")
|
|
return io.BytesIO(FILES["handoff_engine.py"])
|
|
|
|
monkeypatch.setattr(launcher, "urlopen", download)
|
|
with pytest.raises(OSError, match="could not download pinned handoff runtime handoff_sources.py"):
|
|
launcher.runtime_root(plugin)
|
|
assert not cache.exists()
|
|
assert list(cache.parent.iterdir()) == []
|
|
|
|
|
|
def test_hash_mismatch_never_replaces_existing_cache(tmp_path, monkeypatch):
|
|
plugin, cache, _ = installation(tmp_path, monkeypatch)
|
|
launcher.runtime_root(plugin)
|
|
(cache / "handoff_sources.py").write_bytes(b"tampered")
|
|
monkeypatch.setattr(launcher, "urlopen", lambda *args, **kwargs: io.BytesIO(b"wrong response"))
|
|
with pytest.raises(ValueError, match="SHA256 mismatch"):
|
|
launcher.runtime_root(plugin)
|
|
assert (cache / "handoff_engine.py").read_bytes() == FILES["handoff_engine.py"]
|
|
assert (cache / "handoff_sources.py").read_bytes() == b"tampered"
|
|
assert list(cache.parent.iterdir()) == [cache]
|
|
|
|
|
|
def test_tampered_cache_cannot_run_offline(tmp_path, monkeypatch, capsys):
|
|
plugin, cache, _ = installation(tmp_path, monkeypatch)
|
|
launcher.runtime_root(plugin)
|
|
(cache / "handoff_engine.py").write_bytes(b"untrusted code")
|
|
|
|
def offline(*args, **kwargs):
|
|
raise URLError("offline")
|
|
|
|
monkeypatch.setattr(launcher, "urlopen", offline)
|
|
resolve = launcher.runtime_root
|
|
monkeypatch.setattr(launcher, "runtime_root", lambda: resolve(plugin))
|
|
assert launcher.main(["--bundle", "-"]) == 1
|
|
assert "handoff runtime unavailable" in capsys.readouterr().err
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"manifest",
|
|
[
|
|
{"revision": "main", "files": {}},
|
|
{"revision": REVISION, "files": {"../../outside.py": "a" * 64}},
|
|
{"revision": REVISION, "files": {name: "not-a-hash" for name in FILES}},
|
|
[],
|
|
],
|
|
)
|
|
def test_manifest_rejects_unpinned_or_unexpected_files(tmp_path, monkeypatch, manifest):
|
|
plugin, _, requested = installation(tmp_path, monkeypatch)
|
|
(plugin / "handoff-runtime.json").write_text(json.dumps(manifest))
|
|
with pytest.raises(ValueError):
|
|
launcher.runtime_root(plugin)
|
|
assert requested == []
|
|
|
|
|
|
def test_missing_manifest_returns_actionable_error(tmp_path, monkeypatch, capsys):
|
|
resolve = launcher.runtime_root
|
|
monkeypatch.setattr(launcher, "runtime_root", lambda: resolve(tmp_path))
|
|
assert launcher.main([]) == 1
|
|
assert "handoff-runtime.json" in capsys.readouterr().err
|
|
|
|
|
|
def test_launcher_executes_adjacent_engine_with_generic_cli_contract(tmp_path):
|
|
shutil.copy2(CORE / "python" / "session_handoff.py", tmp_path / "session_handoff.py")
|
|
(tmp_path / "handoff_sources.py").write_text("# reader\n")
|
|
(tmp_path / "handoff_engine.py").write_text(
|
|
"import sys\ndef main(argv=None, default_source='unexpected'):\n"
|
|
" assert default_source is None\n assert sys.argv[1:] == ['--bundle', '-']\n return 7\n"
|
|
)
|
|
result = subprocess.run(
|
|
[sys.executable, str(tmp_path / "session_handoff.py"), "--bundle", "-"], capture_output=True
|
|
)
|
|
assert result.returncode == 7, result.stderr.decode()
|
|
|
|
|
|
def test_manifest_declares_only_launcher_and_hashes_pinned_engines():
|
|
manifest = json.loads((CORE / "build" / "handoff-runtime.json").read_text())
|
|
assert manifest["artifacts"] == ["session_handoff.py", "handoff-runtime.json"]
|
|
assert len(manifest["revision"]) == 40 and all(char in "0123456789abcdef" for char in manifest["revision"])
|
|
assert set(manifest["files"]) == launcher.ENGINE_FILES
|
|
for name, digest in manifest["files"].items():
|
|
assert digest == hashlib.sha256((CORE / "python" / name).read_bytes()).hexdigest()
|