from __future__ import annotations import tempfile import unittest from decimal import Decimal from pathlib import Path from agent_integration.client import OpenAgentEvent from agent_integration.service import OpenAgentService, SessionNotFoundError from agent_integration.sessions import SQLiteSessionStore class FakeAgentClient: def __init__(self): self.create_calls = [] self.send_calls = [] self.stream_calls = [] self.run_calls = [] def create_session(self, **kwargs): self.create_calls.append(kwargs) return {"session_id": "open_sess_123", "status": "active"} def send_message(self, session_id, message, **kwargs): self.send_calls.append((session_id, message, kwargs)) return {"session_id": session_id, "run_id": "run_123", "status": "running"} def stream_message(self, session_id, message, **kwargs): self.stream_calls.append((session_id, message, kwargs)) yield OpenAgentEvent(event="message.delta", data={"content": "你"}) def get_run(self, session_id, run_id): self.run_calls.append(("get", session_id, run_id)) return {"session_id": session_id, "run_id": run_id, "status": "completed"} def cancel_run(self, session_id, run_id): self.run_calls.append(("cancel", session_id, run_id)) return {"session_id": session_id, "run_id": run_id, "status": "cancelling"} class AgentServiceTests(unittest.TestCase): def test_session_is_created_once_and_persisted(self): with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "sessions.sqlite3" client = FakeAgentClient() with SQLiteSessionStore(db_path) as store: service = OpenAgentService(client, store) # type: ignore[arg-type] first = service.ensure_session( "conversation-001", external_subject_id="customer-001", metadata={"source": "unittest"}, ) second = service.ensure_session("conversation-001") with SQLiteSessionStore(db_path) as reopened_store: persisted = reopened_store.get("conversation-001") self.assertEqual(first, second) self.assertEqual(first, persisted) self.assertEqual(len(client.create_calls), 1) session_key = client.create_calls[0]["idempotency_key"] self.assertTrue(session_key.startswith("sess_")) self.assertNotIn("conversation-001", session_key) def test_stable_message_id_produces_stable_idempotency_key(self): client = FakeAgentClient() with SQLiteSessionStore(":memory:") as store: service = OpenAgentService(client, store) # type: ignore[arg-type] service.send_message("conversation-001", "first", message_id="upstream-message-001") service.send_message("conversation-001", "retry", message_id="upstream-message-001") self.assertEqual(len(client.create_calls), 1) first_key = client.send_calls[0][2]["idempotency_key"] second_key = client.send_calls[1][2]["idempotency_key"] self.assertEqual(first_key, second_key) self.assertTrue(first_key.startswith("msg_")) self.assertNotIn("upstream-message-001", first_key) def test_stream_get_cancel_and_forget_use_saved_session(self): client = FakeAgentClient() with SQLiteSessionStore(":memory:") as store: service = OpenAgentService(client, store) # type: ignore[arg-type] events = list( service.stream_message( "conversation-001", "你好", message_id="message-001", include_trace=True, ) ) status = service.get_run("conversation-001", "run-001") cancelled = service.cancel_run("conversation-001", "run-001") deleted = service.forget_session("conversation-001") with self.assertRaises(SessionNotFoundError): service.get_session("conversation-001") self.assertEqual(events, [OpenAgentEvent(event="message.delta", data={"content": "你"})]) self.assertEqual(status["status"], "completed") self.assertEqual(cancelled["status"], "cancelling") self.assertTrue(deleted) self.assertTrue(client.stream_calls[0][2]["include_trace"]) self.assertEqual( client.run_calls, [ ("get", "open_sess_123", "run-001"), ("cancel", "open_sess_123", "run-001"), ], ) def test_unknown_conversation_fails_without_remote_call(self): client = FakeAgentClient() with SQLiteSessionStore(":memory:") as store: service = OpenAgentService(client, store) # type: ignore[arg-type] with self.assertRaises(SessionNotFoundError): service.get_run("missing-conversation", "run-001") self.assertEqual(client.run_calls, []) def test_message_whitespace_is_preserved_and_metadata_is_strict_json(self): client = FakeAgentClient() with SQLiteSessionStore(":memory:") as store: service = OpenAgentService(client, store) # type: ignore[arg-type] service.send_message("conversation-001", " keep spacing ", message_id="message-001") with self.assertRaises(ValueError): service.send_message( "conversation-002", "hello", metadata={"not_json": Decimal("1.5")}, ) self.assertEqual(client.send_calls[0][1], " keep spacing ") self.assertEqual(len(client.create_calls), 1) if __name__ == "__main__": unittest.main(verbosity=2)