Files
the-nexus/tests/test_mempalace_searcher.py
Claude (Opus 4.6) e957254b65
Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
[claude] MemPalace × Evennia fleet memory scaffold (#1075) (#1088)
2026-04-07 14:12:38 +00:00

191 lines
6.8 KiB
Python

"""Tests for nexus.mempalace.searcher and nexus.mempalace.config."""
from __future__ import annotations
import os
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from nexus.mempalace.config import CORE_ROOMS, MEMPALACE_PATH, COLLECTION_NAME
from nexus.mempalace.searcher import (
MemPalaceResult,
MemPalaceUnavailable,
_get_client,
search_memories,
add_memory,
)
# ── MemPalaceResult ──────────────────────────────────────────────────────────
def test_result_short_truncates():
r = MemPalaceResult(text="x" * 300, room="forge", wing="bezalel")
short = r.short(200)
assert len(short) <= 204 # 200 + ellipsis
assert short.endswith("")
def test_result_short_no_truncation_needed():
r = MemPalaceResult(text="hello", room="nexus", wing="bezalel")
assert r.short() == "hello"
def test_result_defaults():
r = MemPalaceResult(text="test", room="general", wing="")
assert r.score == 0.0
assert r.source_file == ""
assert r.metadata == {}
# ── Config ───────────────────────────────────────────────────────────────────
def test_core_rooms_contains_required_rooms():
required = {"forge", "hermes", "nexus", "issues", "experiments"}
assert required.issubset(set(CORE_ROOMS))
def test_mempalace_path_env_override(monkeypatch, tmp_path):
monkeypatch.setenv("MEMPALACE_PATH", str(tmp_path))
# Re-import to pick up env var (config reads at import time so we patch)
import importlib
import nexus.mempalace.config as cfg
importlib.reload(cfg)
assert Path(os.environ["MEMPALACE_PATH"]) == tmp_path
importlib.reload(cfg) # restore
# ── _get_client ──────────────────────────────────────────────────────────────
def test_get_client_raises_when_chromadb_missing(tmp_path):
with patch.dict("sys.modules", {"chromadb": None}):
with pytest.raises(MemPalaceUnavailable, match="ChromaDB"):
_get_client(tmp_path)
def test_get_client_raises_when_path_missing(tmp_path):
missing = tmp_path / "nonexistent_palace"
# chromadb importable but path missing
mock_chroma = MagicMock()
with patch.dict("sys.modules", {"chromadb": mock_chroma}):
with pytest.raises(MemPalaceUnavailable, match="Palace directory"):
_get_client(missing)
# ── search_memories ──────────────────────────────────────────────────────────
def _make_mock_collection(docs, metas=None, distances=None):
"""Build a mock ChromaDB collection that returns canned results."""
if metas is None:
metas = [{"room": "forge", "wing": "bezalel", "source_file": ""} for _ in docs]
if distances is None:
distances = [0.1 * i for i in range(len(docs))]
collection = MagicMock()
collection.query.return_value = {
"documents": [docs],
"metadatas": [metas],
"distances": [distances],
}
return collection
def _mock_chroma_client(collection):
client = MagicMock()
client.get_or_create_collection.return_value = collection
return client
def test_search_memories_returns_results(tmp_path):
docs = ["CI pipeline failed on main", "Forge build log 2026-04-01"]
collection = _make_mock_collection(docs)
mock_chroma = MagicMock()
mock_chroma.PersistentClient.return_value = _mock_chroma_client(collection)
with patch.dict("sys.modules", {"chromadb": mock_chroma}):
# Palace path must exist for _get_client check
(tmp_path / "chroma.sqlite3").touch()
results = search_memories("CI failures", palace_path=tmp_path)
assert len(results) == 2
assert results[0].room == "forge"
assert results[0].wing == "bezalel"
assert "CI pipeline" in results[0].text
def test_search_memories_empty_collection(tmp_path):
collection = MagicMock()
collection.query.return_value = {"documents": [[]], "metadatas": [[]], "distances": [[]]}
mock_chroma = MagicMock()
mock_chroma.PersistentClient.return_value = _mock_chroma_client(collection)
with patch.dict("sys.modules", {"chromadb": mock_chroma}):
(tmp_path / "chroma.sqlite3").touch()
results = search_memories("anything", palace_path=tmp_path)
assert results == []
def test_search_memories_with_wing_filter(tmp_path):
docs = ["test doc"]
collection = _make_mock_collection(docs)
mock_chroma = MagicMock()
mock_chroma.PersistentClient.return_value = _mock_chroma_client(collection)
with patch.dict("sys.modules", {"chromadb": mock_chroma}):
(tmp_path / "chroma.sqlite3").touch()
search_memories("query", palace_path=tmp_path, wing="bezalel")
call_kwargs = collection.query.call_args[1]
assert call_kwargs["where"] == {"wing": "bezalel"}
def test_search_memories_with_room_filter(tmp_path):
collection = _make_mock_collection(["doc"])
mock_chroma = MagicMock()
mock_chroma.PersistentClient.return_value = _mock_chroma_client(collection)
with patch.dict("sys.modules", {"chromadb": mock_chroma}):
(tmp_path / "chroma.sqlite3").touch()
search_memories("query", palace_path=tmp_path, room="forge")
call_kwargs = collection.query.call_args[1]
assert call_kwargs["where"] == {"room": "forge"}
def test_search_memories_unavailable(tmp_path):
with patch.dict("sys.modules", {"chromadb": None}):
with pytest.raises(MemPalaceUnavailable):
search_memories("anything", palace_path=tmp_path)
# ── add_memory ───────────────────────────────────────────────────────────────
def test_add_memory_returns_id(tmp_path):
collection = MagicMock()
mock_chroma = MagicMock()
mock_chroma.PersistentClient.return_value = _mock_chroma_client(collection)
with patch.dict("sys.modules", {"chromadb": mock_chroma}):
(tmp_path / "chroma.sqlite3").touch()
doc_id = add_memory(
"We decided to use ChromaDB.",
room="hall_facts",
wing="bezalel",
palace_path=tmp_path,
)
assert isinstance(doc_id, str)
assert len(doc_id) == 36 # UUID format
collection.add.assert_called_once()
call_kwargs = collection.add.call_args[1]
assert call_kwargs["documents"] == ["We decided to use ChromaDB."]
assert call_kwargs["metadatas"][0]["room"] == "hall_facts"
assert call_kwargs["metadatas"][0]["wing"] == "bezalel"