88a1dab810
---ci--- phase: 7 milestone: v0.2 status: complete requirements: covered: [REQ-2-001, REQ-2-002, REQ-2-003, REQ-2-004, REQ-2-005, REQ-2-006, REQ-2-007, REQ-2-008, REQ-2-009, REQ-2-010, REQ-2-011, REQ-2-012] partial: [] ---/ci--- Milestone v0.2 (ai-tutor-architecture) merged to main. Escalation record (audit remediation, durable): P1 executor delegation failed twice (empty subagent results, zero files created); auto-resolved at full autonomy to inline execution with identical plan fidelity (commit 3271373, reflog-only after phase branch squash-delete).
87 lines
2.8 KiB
Python
87 lines
2.8 KiB
Python
"""SessionStore tests — create/append/window/LRU/agent scoping (D-019)."""
|
|
|
|
import pytest
|
|
|
|
from ai_service.agents.session import InMemorySessionStore
|
|
from ai_service.llm.types import Message
|
|
|
|
|
|
def msg(n: int) -> Message:
|
|
return Message(role="user", content=f"m{n}")
|
|
|
|
|
|
async def test_create_and_get():
|
|
store = InMemorySessionStore()
|
|
session = await store.create("s1", agent="coach")
|
|
assert session.agent == "coach"
|
|
assert (await store.get("s1")).session_id == "s1"
|
|
assert await store.get("missing") is None
|
|
|
|
|
|
async def test_append_and_history_window():
|
|
store = InMemorySessionStore()
|
|
await store.create("s1", agent="coach")
|
|
for i in range(30):
|
|
await store.append("s1", msg(i))
|
|
window = await store.history_window("s1", max_messages=20)
|
|
assert len(window) == 20
|
|
assert window[0].content == "m10" # last 20 of m0..m29
|
|
assert window[-1].content == "m29"
|
|
|
|
|
|
async def test_default_window_uses_20():
|
|
store = InMemorySessionStore()
|
|
await store.create("s1", agent="coach")
|
|
for i in range(25):
|
|
await store.append("s1", msg(i))
|
|
window = await store.history_window("s1")
|
|
assert len(window) == 20
|
|
assert window[0].content == "m5"
|
|
|
|
|
|
async def test_lru_eviction_at_cap():
|
|
store = InMemorySessionStore(window=20, max_sessions=3)
|
|
for i in range(3):
|
|
await store.create(f"s{i}", agent="coach")
|
|
# touch s0 so s1 becomes least-recently-used
|
|
await store.get("s0")
|
|
await store.create("s3", agent="coach") # evicts s1
|
|
assert await store.get("s1") is None
|
|
assert await store.get("s0") is not None
|
|
assert await store.get("s2") is not None
|
|
assert await store.get("s3") is not None
|
|
|
|
|
|
async def test_lru_eviction_at_default_500_cap():
|
|
store = InMemorySessionStore() # defaults: window=20, max_sessions=500
|
|
for i in range(500):
|
|
await store.create(f"s{i}", agent="coach")
|
|
await store.get("s0") # touch the oldest → s1 becomes least-recently-used
|
|
await store.create("s500", agent="coach") # evicts s1
|
|
assert await store.get("s1") is None
|
|
assert await store.get("s0") is not None
|
|
assert await store.get("s499") is not None
|
|
assert await store.get("s500") is not None
|
|
|
|
|
|
async def test_append_unknown_session_raises():
|
|
store = InMemorySessionStore()
|
|
with pytest.raises(KeyError):
|
|
await store.append("nope", msg(0))
|
|
|
|
|
|
async def test_delete():
|
|
store = InMemorySessionStore()
|
|
await store.create("s1", agent="coach")
|
|
await store.delete("s1")
|
|
assert await store.get("s1") is None
|
|
|
|
|
|
async def test_sessions_are_agent_scoped():
|
|
store = InMemorySessionStore()
|
|
a = await store.create("coach-session", agent="coach")
|
|
b = await store.create("tutor-session", agent="tutor")
|
|
assert a.agent == "coach"
|
|
assert b.agent == "tutor"
|
|
assert a.session_id != b.session_id
|