168 lines
6.5 KiB
Python
168 lines
6.5 KiB
Python
"""Tests for search_memory_facts function."""
|
|
|
|
import json
|
|
|
|
from deerflow.agents.memory.storage import FileMemoryStorage, create_empty_memory
|
|
from deerflow.agents.memory.updater import search_memory_facts
|
|
|
|
|
|
def _make_fact(content: str, category: str = "context", confidence: float = 0.7) -> dict:
|
|
return {
|
|
"id": f"fact_test_{hash(content) & 0xFFFFFFFF:08x}",
|
|
"content": content,
|
|
"category": category,
|
|
"confidence": confidence,
|
|
"createdAt": "2026-07-09T00:00:00Z",
|
|
"source": "test",
|
|
}
|
|
|
|
|
|
class TestSearchMemoryFacts:
|
|
"""Tests for search_memory_facts function."""
|
|
|
|
def test_basic_substring_match(self, tmp_path, monkeypatch):
|
|
"""Should find facts containing the query string (case-insensitive)."""
|
|
facts = [
|
|
_make_fact("User prefers Python", "preference", 0.9),
|
|
_make_fact("User works with TypeScript", "context", 0.7),
|
|
_make_fact("User lives in Beijing", "personal", 0.8),
|
|
]
|
|
_setup_memory(tmp_path, monkeypatch, facts)
|
|
|
|
results = search_memory_facts("python")
|
|
assert len(results) == 1
|
|
assert results[0]["content"] == "User prefers Python"
|
|
|
|
def test_case_insensitive(self, tmp_path, monkeypatch):
|
|
"""Should match regardless of case."""
|
|
facts = [_make_fact("User prefers Python", "preference", 0.9)]
|
|
_setup_memory(tmp_path, monkeypatch, facts)
|
|
|
|
assert len(search_memory_facts("PYTHON")) == 1
|
|
assert len(search_memory_facts("python")) == 1
|
|
assert len(search_memory_facts("Python")) == 1
|
|
|
|
def test_category_filter(self, tmp_path, monkeypatch):
|
|
"""Should only return facts matching the given category."""
|
|
facts = [
|
|
_make_fact("Likes dark mode", "preference", 0.8),
|
|
_make_fact("Works remotely", "context", 0.7),
|
|
_make_fact("Prefers short answers", "preference", 0.6),
|
|
]
|
|
_setup_memory(tmp_path, monkeypatch, facts)
|
|
|
|
results = search_memory_facts("prefer", category="preference")
|
|
assert len(results) == 1
|
|
assert results[0]["content"] == "Prefers short answers"
|
|
|
|
def test_category_filter_no_match(self, tmp_path, monkeypatch):
|
|
"""Should return empty list when category doesn't match."""
|
|
facts = [_make_fact("Likes dark mode", "preference", 0.8)]
|
|
_setup_memory(tmp_path, monkeypatch, facts)
|
|
|
|
results = search_memory_facts("dark", category="context")
|
|
assert results == []
|
|
|
|
def test_empty_query_returns_empty(self, tmp_path, monkeypatch):
|
|
"""Should return empty list for empty query, not error."""
|
|
facts = [_make_fact("Some fact")]
|
|
_setup_memory(tmp_path, monkeypatch, facts)
|
|
|
|
results = search_memory_facts("")
|
|
assert results == []
|
|
|
|
def test_no_match_returns_empty(self, tmp_path, monkeypatch):
|
|
"""Should return empty list when nothing matches."""
|
|
facts = [_make_fact("User prefers Python")]
|
|
_setup_memory(tmp_path, monkeypatch, facts)
|
|
|
|
results = search_memory_facts("Rust")
|
|
assert results == []
|
|
|
|
def test_sorted_by_confidence_desc(self, tmp_path, monkeypatch):
|
|
"""Should return results sorted by confidence descending."""
|
|
facts = [
|
|
_make_fact("Fact A", confidence=0.3),
|
|
_make_fact("Fact B", confidence=0.9),
|
|
_make_fact("Fact C", confidence=0.6),
|
|
]
|
|
_setup_memory(tmp_path, monkeypatch, facts)
|
|
|
|
results = search_memory_facts("Fact")
|
|
assert len(results) == 3
|
|
assert results[0]["confidence"] == 0.9
|
|
assert results[1]["confidence"] == 0.6
|
|
assert results[2]["confidence"] == 0.3
|
|
|
|
def test_null_confidence_does_not_crash_sort(self, tmp_path, monkeypatch):
|
|
"""A fact stored with ``"confidence": null`` (corrupted/hand-edited memory)
|
|
must not break the confidence sort. ``.get("confidence", 0)`` returns the
|
|
stored ``None`` and comparing None with floats raises TypeError; the coerce
|
|
helper defaults null to a finite midpoint instead."""
|
|
null_fact = {
|
|
"id": "fact_null",
|
|
"content": "Fact with null confidence",
|
|
"category": "context",
|
|
"confidence": None,
|
|
"createdAt": "2026-07-09T00:00:00Z",
|
|
"source": "test",
|
|
}
|
|
facts = [
|
|
_make_fact("Fact high", confidence=0.9),
|
|
null_fact,
|
|
_make_fact("Fact low", confidence=0.2),
|
|
]
|
|
_setup_memory(tmp_path, monkeypatch, facts)
|
|
|
|
# Must not raise TypeError during the confidence sort.
|
|
results = search_memory_facts("Fact")
|
|
|
|
assert len(results) == 3
|
|
# Highest real confidence still sorts first; null (coerced to 0.5) sits
|
|
# between the 0.9 and 0.2 facts.
|
|
assert results[0]["content"] == "Fact high"
|
|
assert {r["content"] for r in results} == {"Fact high", "Fact with null confidence", "Fact low"}
|
|
|
|
def test_respects_limit(self, tmp_path, monkeypatch):
|
|
"""Should return at most `limit` results."""
|
|
facts = [_make_fact(f"Fact {i}", confidence=0.5) for i in range(20)]
|
|
_setup_memory(tmp_path, monkeypatch, facts)
|
|
|
|
results = search_memory_facts("Fact", limit=5)
|
|
assert len(results) == 5
|
|
|
|
def test_negative_limit_returns_empty(self, tmp_path, monkeypatch):
|
|
"""Should not let negative limits expand the result set via slicing."""
|
|
facts = [_make_fact(f"Fact {i}", confidence=0.5) for i in range(3)]
|
|
_setup_memory(tmp_path, monkeypatch, facts)
|
|
|
|
results = search_memory_facts("Fact", limit=-1)
|
|
assert results == []
|
|
|
|
def test_no_facts_returns_empty(self, tmp_path, monkeypatch):
|
|
"""Should return empty list when memory has no facts."""
|
|
_setup_memory(tmp_path, monkeypatch, [])
|
|
|
|
results = search_memory_facts("anything")
|
|
assert results == []
|
|
|
|
|
|
def _setup_memory(tmp_path, monkeypatch, facts: list[dict]):
|
|
"""Set up a FileMemoryStorage with given facts at a temp path."""
|
|
memory_file = tmp_path / "memory.json"
|
|
memory_data = create_empty_memory()
|
|
memory_data["facts"] = facts
|
|
|
|
memory_file.write_text(json.dumps(memory_data))
|
|
|
|
storage = FileMemoryStorage()
|
|
# Force the storage to use our temp file
|
|
monkeypatch.setattr(
|
|
"deerflow.agents.memory.updater.get_memory_storage",
|
|
lambda: storage,
|
|
)
|
|
monkeypatch.setattr(
|
|
"deerflow.agents.memory.updater.get_memory_data",
|
|
lambda agent_name=None, user_id=None: json.loads(memory_file.read_text()),
|
|
)
|