141 lines
4.6 KiB
Python
141 lines
4.6 KiB
Python
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()
|