haiku.rag/tests/tools/test_context.py
Yiorgis Gozadinos da47e3e345
Unify get_typed() into get() with optional type parameter
Fix find_document regression
2026-02-10 14:38:14 +02:00

203 lines
5.4 KiB
Python

from pydantic import BaseModel
from haiku.rag.tools.context import ToolContext
class TestState(BaseModel):
value: int = 0
class TestStateWithList(BaseModel):
items: list[str] = []
def test_tool_context_defaults():
"""Test ToolContext has sensible defaults."""
ctx = ToolContext()
assert ctx._namespaces == {}
def test_register_and_get():
"""Test register and get state for a namespace."""
ctx = ToolContext()
state = TestState(value=42)
ctx.register("test.namespace", state)
retrieved = ctx.get("test.namespace")
assert retrieved is state
assert retrieved.value == 42
def test_get_nonexistent_namespace():
"""Test get returns None for unregistered namespace."""
ctx = ToolContext()
assert ctx.get("nonexistent") is None
def test_get_or_create_creates_new():
"""Test get_or_create creates state when namespace doesn't exist."""
ctx = ToolContext()
state = ctx.get_or_create("test.namespace", TestStateWithList)
assert isinstance(state, TestStateWithList)
assert state.items == []
def test_get_or_create_returns_existing():
"""Test get_or_create returns existing state."""
ctx = ToolContext()
state1 = ctx.get_or_create("test.namespace", TestStateWithList)
state1.items.append("item1")
state2 = ctx.get_or_create("test.namespace", TestStateWithList)
assert state2 is state1
assert state2.items == ["item1"]
def test_clear_namespace():
"""Test clear_namespace removes only the specified namespace."""
ctx = ToolContext()
ctx.register("ns1", TestState(value=1))
ctx.register("ns2", TestState(value=2))
ctx.clear_namespace("ns1")
assert ctx.get("ns1") is None
ns2 = ctx.get("ns2")
assert isinstance(ns2, TestState)
assert ns2.value == 2
def test_clear_namespace_nonexistent():
"""Test clear_namespace handles nonexistent namespace gracefully."""
ctx = ToolContext()
ctx.clear_namespace("nonexistent") # Should not raise
def test_clear_all():
"""Test clear_all clears all namespaces."""
ctx = ToolContext()
ctx.register("ns1", TestState(value=1))
ctx.register("ns2", TestState(value=2))
ctx.clear_all()
assert ctx.get("ns1") is None
assert ctx.get("ns2") is None
def test_namespaces_property():
"""Test namespaces property lists all registered namespaces."""
ctx = ToolContext()
assert ctx.namespaces == []
ctx.register("ns1", TestState())
ctx.register("ns2", TestState())
assert set(ctx.namespaces) == {"ns1", "ns2"}
def test_shared_namespace_between_toolsets():
"""Test that toolsets can share state via the same namespace."""
class SharedState(BaseModel):
citations: dict[str, int] = {}
SHARED_NAMESPACE = "haiku.rag.citations"
ctx = ToolContext()
# First toolset registers the shared state
state1 = ctx.get_or_create(SHARED_NAMESPACE, SharedState)
state1.citations["chunk-a"] = 1
# Second toolset gets the same state
state2 = ctx.get_or_create(SHARED_NAMESPACE, SharedState)
assert state2 is state1
assert state2.citations == {"chunk-a": 1}
# Both see updates
state2.citations["chunk-b"] = 2
assert state1.citations == {"chunk-a": 1, "chunk-b": 2}
def test_dump_namespaces():
"""Test dump_namespaces serializes all registered states."""
ctx = ToolContext()
ctx.register("ns1", TestState(value=10))
ctx.register("ns2", TestStateWithList(items=["a", "b"]))
data = ctx.dump_namespaces()
assert data == {
"ns1": {"value": 10},
"ns2": {"items": ["a", "b"]},
}
def test_load_namespace():
"""Test load_namespace deserializes and registers state."""
ctx = ToolContext()
state = ctx.load_namespace("ns1", TestState, {"value": 42})
assert isinstance(state, TestState)
assert state.value == 42
assert ctx.get("ns1") is state
def test_serialization_roundtrip():
"""Test full serialization/deserialization roundtrip."""
# Create and populate context
original = ToolContext()
original.register("search", TestStateWithList(items=["result1", "result2"]))
original.register("qa", TestState(value=99))
# Serialize
ns_data = original.dump_namespaces()
# Deserialize
restored = ToolContext()
restored.load_namespace("search", TestStateWithList, ns_data["search"])
restored.load_namespace("qa", TestState, ns_data["qa"])
# Verify
search_state = restored.get("search")
assert isinstance(search_state, TestStateWithList)
assert search_state.items == ["result1", "result2"]
qa_state = restored.get("qa")
assert isinstance(qa_state, TestState)
assert qa_state.value == 99
def test_get_with_type_match():
"""Test get with state_type returns typed state when type matches."""
ctx = ToolContext()
state = TestState(value=42)
ctx.register("ns", state)
result = ctx.get("ns", TestState)
assert result is state
assert result.value == 42
def test_get_with_type_mismatch():
"""Test get with state_type returns None when type doesn't match."""
ctx = ToolContext()
ctx.register("ns", TestState(value=42))
result = ctx.get("ns", TestStateWithList)
assert result is None
def test_get_without_type():
"""Test get without state_type returns BaseModel (unchanged behavior)."""
ctx = ToolContext()
state = TestState(value=42)
ctx.register("ns", state)
result = ctx.get("ns")
assert result is state