""" Unit tests for the Redis short-term memory sliding window. Uses fakeredis to avoid requiring a real Redis connection. All tests verify tenant+agent+user namespacing and RPUSH/LTRIM correctness. """ from __future__ import annotations import json import fakeredis.aioredis import pytest from orchestrator.memory.short_term import append_message, get_recent_messages @pytest.fixture async def redis(): """Return a fakeredis async client for testing.""" client = fakeredis.aioredis.FakeRedis() yield client await client.aclose() TENANT = "tenant-abc" AGENT = "agent-xyz" USER = "user-123" async def test_get_recent_messages_empty(redis): """get_recent_messages on empty key returns empty list.""" result = await get_recent_messages(redis, TENANT, AGENT, USER) assert result == [] async def test_append_and_get_single_message(redis): """append_message stores a message; get_recent_messages retrieves it.""" await append_message(redis, TENANT, AGENT, USER, role="user", content="Hello!") result = await get_recent_messages(redis, TENANT, AGENT, USER) assert len(result) == 1 assert result[0] == {"role": "user", "content": "Hello!"} async def test_append_multiple_messages_ordering(redis): """Messages are returned in insertion order (oldest first).""" await append_message(redis, TENANT, AGENT, USER, role="user", content="First") await append_message(redis, TENANT, AGENT, USER, role="assistant", content="Second") await append_message(redis, TENANT, AGENT, USER, role="user", content="Third") result = await get_recent_messages(redis, TENANT, AGENT, USER) assert len(result) == 3 assert result[0]["content"] == "First" assert result[1]["content"] == "Second" assert result[2]["content"] == "Third" async def test_sliding_window_trims_to_window_size(redis): """append_message with window=5 keeps only last 5 messages.""" for i in range(10): await append_message(redis, TENANT, AGENT, USER, role="user", content=f"msg-{i}", window=5) result = await get_recent_messages(redis, TENANT, AGENT, USER, n=20) assert len(result) == 5 # Should have the last 5 messages: msg-5 through msg-9 contents = [m["content"] for m in result] assert contents == ["msg-5", "msg-6", "msg-7", "msg-8", "msg-9"] async def test_default_window_20(redis): """Default window is 20 — 21st message pushes out the first.""" for i in range(21): await append_message(redis, TENANT, AGENT, USER, role="user", content=f"msg-{i}") result = await get_recent_messages(redis, TENANT, AGENT, USER) assert len(result) == 20 assert result[0]["content"] == "msg-1" assert result[-1]["content"] == "msg-20" async def test_get_recent_messages_n_parameter(redis): """get_recent_messages n parameter limits results.""" for i in range(10): await append_message(redis, TENANT, AGENT, USER, role="user", content=f"msg-{i}") result = await get_recent_messages(redis, TENANT, AGENT, USER, n=3) assert len(result) == 3 # n=3 returns last 3: msg-7, msg-8, msg-9 contents = [m["content"] for m in result] assert contents == ["msg-7", "msg-8", "msg-9"] async def test_key_namespacing_user_isolation(redis): """Different users of the same agent have isolated memory.""" await append_message(redis, TENANT, AGENT, "user-A", role="user", content="User A message") await append_message(redis, TENANT, AGENT, "user-B", role="user", content="User B message") result_a = await get_recent_messages(redis, TENANT, AGENT, "user-A") result_b = await get_recent_messages(redis, TENANT, AGENT, "user-B") assert len(result_a) == 1 assert result_a[0]["content"] == "User A message" assert len(result_b) == 1 assert result_b[0]["content"] == "User B message" async def test_key_namespacing_tenant_isolation(redis): """Different tenants with same agent+user IDs have isolated memory.""" await append_message(redis, "tenant-1", AGENT, USER, role="user", content="Tenant 1 message") await append_message(redis, "tenant-2", AGENT, USER, role="user", content="Tenant 2 message") result_1 = await get_recent_messages(redis, "tenant-1", AGENT, USER) result_2 = await get_recent_messages(redis, "tenant-2", AGENT, USER) assert result_1[0]["content"] == "Tenant 1 message" assert result_2[0]["content"] == "Tenant 2 message" async def test_key_namespacing_agent_isolation(redis): """Different agents for the same tenant+user have isolated memory.""" await append_message(redis, TENANT, "agent-1", USER, role="user", content="Agent 1 context") await append_message(redis, TENANT, "agent-2", USER, role="user", content="Agent 2 context") result_1 = await get_recent_messages(redis, TENANT, "agent-1", USER) result_2 = await get_recent_messages(redis, TENANT, "agent-2", USER) assert result_1[0]["content"] == "Agent 1 context" assert result_2[0]["content"] == "Agent 2 context" async def test_message_role_and_content_round_trip(redis): """Messages store and retrieve role + content correctly.""" await append_message(redis, TENANT, AGENT, USER, role="assistant", content="I can help you with that.") result = await get_recent_messages(redis, TENANT, AGENT, USER) msg = result[0] assert msg["role"] == "assistant" assert msg["content"] == "I can help you with that." # Verify it has exactly role and content keys assert set(msg.keys()) == {"role", "content"}