"""Unit tests for navi.tools.scratchpad."""
import pytest
from navi.tools.scratchpad import ScratchpadTool, get_section, _kv_store
from navi.tools._internal.base import current_session_id, current_user_id
from navi.store import KvStore
from tests.conftest_factory import FakePool
class FakeKvStore(KvStore):
def __init__(self):
self._data: dict[tuple, str] = {}
async def _get_pool(self):
return FakePool()
async def get(self, user_id, session_id, scope, key):
return self._data.get((user_id or "", session_id, scope, key))
async def set(self, user_id, session_id, scope, key, value):
self._data[(user_id or "", session_id, scope, key)] = value
async def get_all(self, user_id, session_id, scope):
return {
k[3]: v
for k, v in self._data.items()
if k[:3] == (user_id or "", session_id, scope)
}
async def delete(self, user_id, session_id, scope, key):
self._data.pop((user_id or "", session_id, scope, key), None)
async def clear_scope(self, user_id, session_id, scope):
keys = [k for k in self._data if k[:3] == (user_id or "", session_id, scope)]
for k in keys:
del self._data[k]
@pytest.fixture(autouse=True)
def _fake_kv():
store = FakeKvStore()
from navi.tools import scratchpad as _mod
_mod._kv_store = store
yield store
_mod._kv_store = None
@pytest.fixture(autouse=True)
def _fake_ctx():
token = current_session_id.set("sess1")
token2 = current_user_id.set("user1")
yield
current_session_id.reset(token)
current_user_id.reset(token2)
@pytest.mark.asyncio
async def test_write_then_read(_fake_kv):
tool = ScratchpadTool()
result = await tool.execute({"op": "write", "section": "findings", "content": "found X"})
assert result.success is True
result = await tool.execute({"op": "read", "section": "findings"})
assert result.success is True
assert "found X" in result.output
@pytest.mark.asyncio
async def test_append(_fake_kv):
tool = ScratchpadTool()
await tool.execute({"op": "write", "section": "findings", "content": "line1"})
result = await tool.execute({"op": "append", "section": "findings", "content": "line2"})
assert result.success is True
result = await tool.execute({"op": "read", "section": "findings"})
assert "line1" in result.output
assert "line2" in result.output
@pytest.mark.asyncio
async def test_read_all_sections(_fake_kv):
tool = ScratchpadTool()
await tool.execute({"op": "write", "section": "goal", "content": "do thing"})
await tool.execute({"op": "write", "section": "findings", "content": "found"})
result = await tool.execute({"op": "read"})
assert result.success is True
assert "goal" in result.output
assert "findings" in result.output
@pytest.mark.asyncio
async def test_clear_section(_fake_kv):
tool = ScratchpadTool()
await tool.execute({"op": "write", "section": "goal", "content": "do thing"})
result = await tool.execute({"op": "clear", "section": "goal"})
assert result.success is True
result = await tool.execute({"op": "read", "section": "goal"})
assert "empty" in result.output.lower()
@pytest.mark.asyncio
async def test_scope_isolation(_fake_kv):
tool = ScratchpadTool()
await tool.execute({"op": "write", "section": "main", "content": "sess1 data"})
current_session_id.set("sess2")
result = await tool.execute({"op": "read", "section": "main"})
assert "empty" in result.output.lower()
@pytest.mark.asyncio
async def test_get_section(_fake_kv):
tool = ScratchpadTool()
await tool.execute({"op": "write", "section": "artifacts", "content": "path/to/file"})
text = await get_section("sess1", "artifacts")
assert text == "path/to/file"