import pathlib import tempfile import uuid import pytest from agentmsgs.stores.sqlite import SQLiteStore def test_sqlite_store_requires_explicit_path(): with pytest.raises(TypeError): SQLiteStore() def test_sqlite_persists_across_handles(): with tempfile.TemporaryDirectory() as d: p = pathlib.Path(d) / "test.db" s1 = SQLiteStore(p) try: a = s1.get_or_create_agent("Alice") b = s1.get_or_create_agent("Bob") t = s1.create_thread({a, b}) s1.append_message(t.id, a, "hi") # new handle same file s2 = SQLiteStore(p) try: a2 = s2.get_agent_by_name("Alice") assert a2 is not None assert a2.id == a.id assert len(s2.list_messages(t.id)) == 1 assert s2.find_threads({a})[0].id == t.id finally: s2.close() finally: s1.close() def test_sqlite_concurrent_append(): import pathlib import tempfile from agentmsgs.stores.sqlite import SQLiteStore with tempfile.TemporaryDirectory() as d: p = pathlib.Path(d) / "c.db" s1 = SQLiteStore(p) s2 = SQLiteStore(p) try: a = s1.get_or_create_agent("A") b = s1.get_or_create_agent("B") # s2 sees same agents via file a2 = s2.get_agent_by_name("A") b2 = s2.get_agent_by_name("B") assert a2 is not None assert b2 is not None assert a.id == a2.id assert b.id == b2.id t = s1.create_thread({a, b}) s1.append_message(t.id, a, "from s1") s2.append_message(t.id, b2, "from s2") assert len(s1.list_messages(t.id)) == 2 # verify seq uniqueness and monotonicity msgs = s1.list_messages(t.id) seqs = [m.seq for m in msgs] assert seqs == [1, 2] assert len(set(seqs)) == 2 finally: s2.close() s1.close() def test_sqlite_thread_info_and_read_state(): with tempfile.TemporaryDirectory() as d: p = pathlib.Path(d) / "info.db" s = SQLiteStore(p) try: a = s.get_or_create_agent("A") b = s.get_or_create_agent("B") t = s.create_thread({a, b}) s.append_message(t.id, a, "one") s.append_message(t.id, b, "two") latest = s.append_message(t.id, a, "three") info = s.get_thread_info(t.id) assert info is not None assert info.thread == t assert info.message_count == 3 assert info.latest_message == latest state = s.get_thread_read_state(t.id, a) assert state.agent == a assert state.cursor_seq == 0 assert state.latest_seq == 3 assert state.unread_count == 3 assert state.unread_from_others_count == 1 s.set_cursor(t.id, a, 2) state = s.get_thread_read_state(t.id, a) assert state.cursor_seq == 2 assert state.unread_count == 1 assert state.unread_from_others_count == 0 assert s.get_thread_info(uuid.uuid4()) is None assert s.get_thread_read_state(uuid.uuid4(), a) is None finally: s.close() def test_sqlite_thread_read_state_uses_consistent_snapshot(): with tempfile.TemporaryDirectory() as d: p = pathlib.Path(d) / "snapshot.db" reader = SQLiteStore(p) writer = SQLiteStore(p) try: a = reader.get_or_create_agent("A") b = reader.get_or_create_agent("B") thread = reader.create_thread({a, b}) reader.append_message(thread.id, a, "one") injected = False def append_before_unread_query(statement: str) -> None: nonlocal injected if ( not injected and statement.startswith("SELECT COUNT(*) FROM messages") and " AND seq>" in statement and "sender_id" not in statement ): injected = True writer.append_message(thread.id, b, "concurrent") reader._db.set_trace_callback(append_before_unread_query) state = reader.get_thread_read_state(thread.id, a) assert injected is True assert state.latest_seq == 1 assert state.unread_count == 1 assert state.unread_from_others_count == 0 finally: reader.close() writer.close()