brain / tests /test_brain_lifecycle.py
Leon4gr45's picture
Deploy complete BrainAPI source 42cde77950db
6663ad4 verified
Raw History Blame Contribute Delete
3.97 kB
from __future__ import annotations
import pytest
from src.services.brain_lifecycle import (
BrainCleanupError,
BrainLifecycleResult,
BrainLifecycleService,
BrainNotFoundError,
InvalidBrainIdError,
validate_brain_id,
)
class FakeStorage:
def __init__(self, name: str, events: list[str], fail: bool = False):
self.name = name
self.events = events
self.fail = fail
def clear_brain_data(self, brain_id: str) -> None:
self.events.append(f"clear:{self.name}:{brain_id}")
if self.fail:
raise RuntimeError(f"{self.name} unavailable")
class FakeData(FakeStorage):
def __init__(
self,
events: list[str],
*,
exists: bool = True,
fail: bool = False,
):
super().__init__("data", events, fail)
self.exists = exists
def get_brain(self, name_key: str):
self.events.append(f"get:{name_key}")
return {"name_key": name_key, "pat": "pat"} if self.exists else None
def delete_brain_registry(self, brain_id: str) -> bool:
self.events.append(f"registry:delete:{brain_id}")
was_present = self.exists
self.exists = False
return was_present
class FakeCache(FakeStorage):
client = None
def __init__(self, events: list[str], fail: bool = False):
super().__init__("cache", events, fail)
def clear_brain_registry_cache(self, brain_id: str) -> None:
self.events.append(f"registry-cache:clear:{brain_id}")
def service(
events: list[str],
*,
exists: bool = True,
failing_backend: str | None = None,
) -> tuple[BrainLifecycleService, FakeData]:
data = FakeData(events, exists=exists, fail=failing_backend == "data")
return (
BrainLifecycleService(
data_client=data,
graph_client=FakeStorage(
"graph", events, fail=failing_backend == "graph"
),
vector_client=FakeStorage(
"vectors", events, fail=failing_backend == "vectors"
),
cache_client=FakeCache(events, fail=failing_backend == "cache"),
),
data,
)
def test_clear_preserves_brain_identity_and_clears_every_backend():
events: list[str] = []
lifecycle, data = service(events)
result = lifecycle.clear("demo")
assert result == BrainLifecycleResult(
brain_id="demo",
operation="clear",
existed=True,
cleared_backends=["data", "graph", "vectors", "cache"],
)
assert data.exists is True
assert "registry:delete:demo" not in events
def test_clear_rejects_an_unknown_brain_without_touching_storage():
events: list[str] = []
lifecycle, _ = service(events, exists=False)
with pytest.raises(BrainNotFoundError):
lifecycle.clear("missing")
assert events == ["get:missing"]
def test_delete_is_idempotent_and_invalidates_registry_cache():
events: list[str] = []
lifecycle, data = service(events, exists=False)
result = lifecycle.delete("gone")
assert result.existed is False
assert result.operation == "delete"
assert data.exists is False
assert events[-2:] == [
"registry-cache:clear:gone",
"registry:delete:gone",
]
def test_partial_cleanup_keeps_registry_for_a_safe_retry():
events: list[str] = []
lifecycle, data = service(events, failing_backend="graph")
with pytest.raises(BrainCleanupError) as caught:
lifecycle.delete("demo")
assert caught.value.failed_backends == ["graph"]
assert data.exists is True
assert "clear:vectors:demo" in events
assert "clear:cache:demo" in events
assert "registry:delete:demo" not in events
@pytest.mark.parametrize("brain_id", ["", "bad id", "-bad", "system", "SYSTEM", "a" * 129])
def test_lifecycle_rejects_invalid_or_reserved_brain_ids(brain_id: str):
with pytest.raises(InvalidBrainIdError):
validate_brain_id(brain_id)