haiku.rag/tests/capabilities/test_borrowed_client.py
Yiorgis Gozadinos 0882dc9fed
Lend the caller's client to the capability
`client.ask` and `client.analyze` built their capability from a db_path, so the
capability opened a second connection to the database the client already had
open, once per call.

Ownership is now explicit rather than inferred. `rag` stays the connection the
capability opened and must close; `borrowed_rag` is a caller's, which
`_ensure_rag` prefers and `_close` never touches. Two fields rather than a flag,
so closing a borrowed connection is not expressible.

`for_run` still clears `rag` per run, since a run owns what it opens. It leaves
`borrowed_rag` alone: that connection belongs to the caller and outlives the run.
2026-08-18 14:58:18 +03:00

132 lines
4.3 KiB
Python

import pytest
from haiku.rag.capabilities.rag import create_capability
from haiku.rag.client import HaikuRAG
@pytest.mark.asyncio
async def test_a_borrowed_client_is_reused_not_reopened(temp_db_path, monkeypatch):
"""A capability handed a client must not open a second connection to the
same database."""
from haiku.rag.store.engine import Store
async with HaikuRAG(temp_db_path, create=True) as client:
opens = 0
initialize = Store._initialize
async def counted(self):
nonlocal opens
opens += 1
return await initialize(self)
monkeypatch.setattr(Store, "_initialize", counted)
capability = create_capability(
db_path=client.store.db_path, config=client._config, rag=client
)
assert await capability._ensure_rag() is client
assert opens == 0
@pytest.mark.asyncio
async def test_closing_never_closes_a_borrowed_client(temp_db_path):
"""`_close` owns only what it opened. Closing the caller's client would be a
use-after-close for the caller."""
async with HaikuRAG(temp_db_path, create=True) as client:
capability = create_capability(
db_path=client.store.db_path, config=client._config, rag=client
)
await capability._ensure_rag()
await capability._close()
# Still usable by its owner.
assert await client.list_documents() == []
@pytest.mark.asyncio
async def test_a_borrowed_client_survives_for_run(temp_db_path):
"""for_run clears the owned connection per run; a borrowed one is the
caller's and carries into the run copy."""
from tests.capabilities.test_capabilities import Deps, make_context
async with HaikuRAG(temp_db_path, create=True) as client:
capability = create_capability(
db_path=client.store.db_path, config=client._config, rag=client
)
run_capability = await capability.for_run(make_context(Deps()))
assert run_capability is not capability
assert run_capability.rag is None
assert run_capability.borrowed_rag is client
assert await run_capability._ensure_rag() is client
@pytest.mark.asyncio
async def test_ask_hands_its_client_to_the_capability(temp_db_path, monkeypatch):
"""`ask` built the capability from a db_path alone, so the capability opened
its own connection to a database the client already had open."""
from haiku.rag.capabilities import rag as rag_capability
from haiku.rag.store.engine import Store
real = rag_capability.create_capability
built = {}
def spy(**kwargs):
built["capability"] = real(**kwargs)
raise RuntimeError("stop before running the agent")
async with HaikuRAG(temp_db_path, create=True) as client:
monkeypatch.setattr(rag_capability, "create_capability", spy)
with pytest.raises(RuntimeError, match="stop before running the agent"):
await client.ask("anything")
capability = built["capability"]
opens = 0
initialize = Store._initialize
async def counted(self):
nonlocal opens
opens += 1
return await initialize(self)
monkeypatch.setattr(Store, "_initialize", counted)
assert await capability._ensure_rag() is client
assert opens == 0
@pytest.mark.asyncio
async def test_analyze_hands_its_client_to_the_capability(temp_db_path, monkeypatch):
from haiku.rag.capabilities import analysis as analysis_capability
from haiku.rag.store.engine import Store
real = analysis_capability.create_capability
built = {}
def spy(**kwargs):
built["capability"] = real(**kwargs)
raise RuntimeError("stop before running the agent")
async with HaikuRAG(temp_db_path, create=True) as client:
monkeypatch.setattr(analysis_capability, "create_capability", spy)
with pytest.raises(RuntimeError, match="stop before running the agent"):
await client.analyze("anything")
capability = built["capability"]
opens = 0
initialize = Store._initialize
async def counted(self):
nonlocal opens
opens += 1
return await initialize(self)
monkeypatch.setattr(Store, "_initialize", counted)
assert await capability._ensure_rag() is client
assert opens == 0