"""Tests for the synapse_instructions tool and the hidden dispatcher profile."""
import pytest
from navi.profiles import ALL_PROFILES
from navi.core.registry import ProfileRegistry
from navi.tools import SynapseInstructionsTool
from navi.tools._internal.base import ToolContext
def _tool_with_store(monkeypatch, store):
async def fake_get(self, user_id):
return store
monkeypatch.setattr(
"navi.synapse.settings_store.SynapseSettingsStore.get",
fake_get,
)
updates = []
async def fake_save_instructions(self, user_id, content, edited_by, reason=None):
updates.append((user_id, content, edited_by, reason))
store.instructions = content
monkeypatch.setattr(
"navi.synapse.settings_store.SynapseSettingsStore.save_instructions",
fake_save_instructions,
)
return SynapseInstructionsTool(pool_provider=lambda: None), updates
async def test_read_empty(monkeypatch):
from navi.synapse.settings_store import SynapseSettings
tool, _ = _tool_with_store(monkeypatch, SynapseSettings(user_id="u1"))
ctx = ToolContext(session_id="s1", user_id="u1")
result = await tool.execute({"op": "read"}, ctx=ctx)
assert result.success
assert "empty" in result.output.lower()
async def test_update_records_navi_edit(monkeypatch):
from navi.synapse.settings_store import SynapseSettings
tool, updates = _tool_with_store(monkeypatch, SynapseSettings(user_id="u1"))
ctx = ToolContext(session_id="s1", user_id="u1")
result = await tool.execute(
{"op": "update", "content": "gntodo → developer", "reason": " clarified scope"},
ctx=ctx,
)
assert result.success
assert updates == [("u1", "gntodo → developer", "navi", "clarified scope")]
async def test_update_requires_content(monkeypatch):
from navi.synapse.settings_store import SynapseSettings
tool, updates = _tool_with_store(monkeypatch, SynapseSettings(user_id="u1"))
ctx = ToolContext(session_id="s1", user_id="u1")
result = await tool.execute({"op": "update"}, ctx=ctx)
assert not result.success
assert updates == []
async def test_no_user_context_fails(monkeypatch):
from navi.synapse.settings_store import SynapseSettings
tool, _ = _tool_with_store(monkeypatch, SynapseSettings(user_id="u1"))
result = await tool.execute({"op": "read"})
assert not result.success
assert "user" in result.error.lower()
def test_dispatcher_profile_is_hidden():
"""The dispatcher profile exists, is hidden, and the registry serves it."""
ids = [p.id for p in ALL_PROFILES]
assert "dispatcher" in ids
assert "secretary" in ids
reg = ProfileRegistry()
for p in ALL_PROFILES:
reg.register(p)
dispatcher = reg.get("dispatcher")
assert dispatcher.is_hidden is True
listed = [p.id for p in reg.all() if not getattr(p, "is_hidden", False)]
assert "dispatcher" not in listed
# ── real pool wiring ─────────────────────────────────────────────────────────
def _settings_row(conn, **overrides):
from tests.conftest_factory import FakeRecord
row = dict(
user_id="u1", reactions_enabled=True, push_target="app",
completion_notify="important", instructions="react to mentions",
updated_at=None,
)
row.update(overrides)
conn.enqueue(FakeRecord(**row))
return conn
async def test_read_goes_through_the_real_pool(monkeypatch):
"""Regression: the registry wires `pool_provider` to PgSessionStore._get_pool,
which is async — the tool used to pass that coroutine into
SynapseSettingsStore and die with
"'coroutine' object has no attribute 'fetchrow'".
SynapseSettingsStore is not mocked here: the document comes from the fake
asyncpg connection.
"""
import inspect
from tests.conftest_factory import FakeConnection, FakePool
from navi.tools import SynapseInstructionsTool
conn = _settings_row(FakeConnection())
pool = FakePool(conn)
async def provider(): # what navi/core/registry.py passes: a bound _get_pool
return pool
result = await SynapseInstructionsTool(pool_provider=provider).execute(
{"op": "read"}, ctx=ToolContext(session_id="s1", user_id="u1")
)
assert result.success, result.error
assert result.output == "react to mentions"
assert [c[0] for c in conn.calls] == ["fetchrow"]
async def test_sync_pool_provider_still_accepted(monkeypatch):
"""A provider that hands back an already-resolved pool keeps working."""
from tests.conftest_factory import FakeConnection, FakePool
from navi.tools import SynapseInstructionsTool
conn = _settings_row(FakeConnection(), instructions="watch gntodo")
pool = FakePool(conn)
result = await SynapseInstructionsTool(pool_provider=lambda: pool).execute(
{"op": "read"}, ctx=ToolContext(session_id="s1", user_id="u1")
)
assert result.success, result.error
assert result.output == "watch gntodo"