Newer
Older
navi-1 / tests / unit / tools / test_memory_search_tool.py
"""The `memory_search` tool — argument handling and output shape."""

from datetime import datetime, timezone

from navi.memory.search import MemoryHit
from navi.tools._internal.base import ToolContext
from navi.tools.memory_search import MemorySearchTool

NOW = datetime(2026, 11, 9, 12, 0, tzinfo=timezone.utc)


class FakeSearch:
    def __init__(self, hits=None):
        self.hits = hits or []
        self.calls = []

    async def search(self, query, **kwargs):
        self.calls.append((query, kwargs))
        return self.hits

    async def latest_summary_at(self, session_id):
        return NOW if session_id == "s1" else None


class TestArguments:
    async def test_a_missing_query_is_an_error(self):
        result = await MemorySearchTool(FakeSearch()).execute({})
        assert result.success is False
        assert result.error == "missing query"

    async def test_defaults_are_scope_all_and_k_five(self):
        search = FakeSearch()
        await MemorySearchTool(search).execute({"query": "port"})
        query, kwargs = search.calls[0]
        assert query == "port"
        assert kwargs["scope"] == "all"
        assert kwargs["k"] == 5

    async def test_an_invalid_scope_falls_back_to_all(self):
        search = FakeSearch()
        await MemorySearchTool(search).execute({"query": "port", "scope": "everything"})
        assert search.calls[0][1]["scope"] == "all"

    async def test_k_is_clamped(self):
        search = FakeSearch()
        tool = MemorySearchTool(search)
        await tool.execute({"query": "x", "k": 500})
        await tool.execute({"query": "x", "k": "nope"})
        assert search.calls[0][1]["k"] == 20
        assert search.calls[1][1]["k"] == 5

    async def test_user_and_session_come_from_the_tool_context(self):
        search = FakeSearch()
        ctx = ToolContext(session_id="s1", user_id="u1")
        await MemorySearchTool(search).execute({"query": "x"}, ctx)
        kwargs = search.calls[0][1]
        assert kwargs["user_id"] == "u1"
        assert kwargs["current_session_id"] == "s1"
        assert kwargs["summary_at"] == NOW


class TestOutput:
    async def test_hits_are_rendered_with_provenance(self):
        hits = [
            MemoryHit(source_type="fact", text="server_port: 8099", key="server_port",
                      category="technical", timestamp=NOW),
            MemoryHit(source_type="raw", text="we set the port to 8099",
                      session_id="s1", start_seq=3, end_seq=4, timestamp=NOW),
        ]
        result = await MemorySearchTool(FakeSearch(hits)).execute({"query": "port"})
        assert result.success is True
        assert "fact [technical]" in result.output
        assert "raw · session s1 · turns 3–4" in result.output