nanobot/tests/extensions/test_store.py

145 lines
4.8 KiB
Python

import json
from pathlib import Path
import pytest
from nanobot.extensions.store import (
ExtensionSourceKind,
ExtensionStore,
InstalledExtension,
)
def _package(root: Path, *, version: str = "1.0.0") -> Path:
root.mkdir()
(root / "extension.py").write_text("def register(api):\n pass\n")
(root / "nanobot.extension.json").write_text(
json.dumps(
{
"id": "store.test",
"name": "Store test",
"version": version,
"entry": "extension:register",
"permissions": [
{"name": "network", "reason": "Fetch selected sources."}
],
}
)
)
return root
def test_local_install_is_atomic_and_untrusted_by_default(tmp_path: Path) -> None:
store = ExtensionStore(tmp_path / "installed")
result = store.install_local(_package(tmp_path / "source"))
assert result.record.source is ExtensionSourceKind.LOCAL
assert result.record.integrity.startswith("sha256:")
assert result.manifest.id == "store.test"
assert not store.discover().candidates[0].trusted
def test_policy_updates_survive_discovery(tmp_path: Path) -> None:
store = ExtensionStore(tmp_path / "installed")
store.install_local(_package(tmp_path / "source"))
store.set_trusted("store.test", True)
store.set_enabled("store.test", False)
store.set_permissions("store.test", {"network"})
candidate = store.discover().candidates[0]
assert candidate.trusted
assert not candidate.enabled
assert candidate.granted_permissions == frozenset({"network"})
def test_unknown_permission_cannot_be_granted(tmp_path: Path) -> None:
store = ExtensionStore(tmp_path / "installed")
store.install_local(_package(tmp_path / "source"))
with pytest.raises(ValueError, match="not requested"):
store.set_permissions("store.test", {"filesystem.write"})
def test_modified_install_loses_effective_trust(tmp_path: Path) -> None:
store = ExtensionStore(tmp_path / "installed")
store.install_local(_package(tmp_path / "source"), trusted=True)
(store.root / "store.test" / "extension.py").write_text("changed = True\n")
discovery = store.discover()
assert not discovery.candidates[0].trusted
assert not discovery.candidates[0].integrity_valid
assert discovery.diagnostics[0].code == "extension_integrity_mismatch"
def test_changed_reinstall_revokes_trust_but_identical_reinstall_preserves_it(
tmp_path: Path,
) -> None:
source = _package(tmp_path / "source")
store = ExtensionStore(tmp_path / "installed")
store.install_local(source, trusted=True)
identical = store.install_local(source)
assert identical.record.trusted
(source / "extension.py").write_text("changed = True\n")
changed = store.install_local(source)
assert not changed.record.trusted
def test_install_rejects_missing_manifest_and_symlinks(tmp_path: Path) -> None:
store = ExtensionStore(tmp_path / "installed")
empty = tmp_path / "empty"
empty.mkdir()
with pytest.raises(ValueError, match="cannot read extension manifest"):
store.install_local(empty)
source = _package(tmp_path / "source")
(source / "outside").symlink_to(tmp_path)
with pytest.raises(ValueError, match="symlink"):
store.install_local(source)
def test_install_rejects_source_containing_store(tmp_path: Path) -> None:
source = _package(tmp_path / "source")
store = ExtensionStore(source / "installed")
with pytest.raises(ValueError, match="cannot contain the extension store"):
store.install_local(source)
def test_registry_validation_and_uninstall(tmp_path: Path) -> None:
with pytest.raises(ValueError, match="sha256 digest"):
InstalledExtension.model_validate(
{
"id": "sample",
"version": "1.0.0",
"source": "local",
"source_ref": "/tmp/sample",
"integrity": "invalid",
"installed_at": "2026-01-01T00:00:00Z",
}
)
store = ExtensionStore(tmp_path / "installed")
store.install_local(_package(tmp_path / "source"))
store.uninstall("store.test")
assert store.records() == {}
assert not (store.root / "store.test").exists()
def test_corrupt_registry_is_diagnostic_and_not_overwritten(tmp_path: Path) -> None:
store = ExtensionStore(tmp_path / "installed")
store.install_local(_package(tmp_path / "source"))
store.registry_path.write_text("{broken")
discovery = store.discover()
assert discovery.diagnostics[0].code == "invalid_extension_registry"
with pytest.raises(ValueError, match="invalid extension registry"):
store.set_trusted("store.test", True)
assert store.registry_path.read_text() == "{broken"