Spaces:
Sleeping
Sleeping
File size: 7,632 Bytes
d9d9785 7d506e1 d9d9785 2715896 7d506e1 2715896 d9d9785 2715896 7d506e1 1b98491 7d506e1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | """Unit tests for the optional durable session store abstraction."""
from datetime import datetime
import pytest
from agent.core.session_persistence import (
MongoSessionStore,
NoopSessionStore,
USAGE_EVENT_TYPES,
_safe_message_doc,
)
@pytest.mark.asyncio
async def test_noop_store_keeps_local_cli_and_tests_db_free():
store = NoopSessionStore()
await store.init()
await store.upsert_session(session_id="s1", user_id="u1", model="m")
await store.save_snapshot(
session_id="s1",
user_id="u1",
model="m",
messages=[{"role": "user", "content": "hello"}],
)
assert await store.load_session("s1") is None
assert await store.list_sessions("u1") == []
assert await store.append_event("s1", "processing", {}) is None
def test_unsafe_message_payload_is_replaced_with_marker():
marker = _safe_message_doc({"role": "assistant", "content": object()})
assert marker["role"] == "tool"
assert marker["ml_intern_persistence_error"] == "message_too_large_or_invalid"
# ββ mark_pro_seen βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
class _FakeProUsers:
"""In-memory stand-in for the ``pro_users`` collection.
Supports just enough of the Motor API to exercise ``mark_pro_seen``:
``update_one`` with ``$setOnInsert`` + ``$set`` + ``upsert=True``, and
``find_one_and_update`` with the guarded filter the conversion check uses.
"""
def __init__(self) -> None:
self.docs: dict[str, dict] = {}
async def update_one(self, filt, update, upsert=False):
_id = filt["_id"]
doc = self.docs.get(_id)
if doc is None and upsert:
doc = dict(update.get("$setOnInsert") or {})
self.docs[_id] = doc
if doc is None:
return
for k, v in (update.get("$set") or {}).items():
doc[k] = v
async def find_one_and_update(self, filt, update, return_document=None):
_id = filt["_id"]
doc = self.docs.get(_id)
if doc is None:
return None
# Guard checks the conversion test uses: ever_non_pro=True AND
# first_seen_pro_at missing.
for k, v in filt.items():
if k == "_id":
continue
if isinstance(v, dict) and "$exists" in v:
if v["$exists"] and k not in doc:
return None
if not v["$exists"] and k in doc:
return None
elif doc.get(k) != v:
return None
for k, v in (update.get("$set") or {}).items():
doc[k] = v
return dict(doc)
class _FakeDB:
def __init__(self) -> None:
self.pro_users = _FakeProUsers()
def _store_with_fake_db() -> MongoSessionStore:
s = MongoSessionStore.__new__(MongoSessionStore)
s.enabled = True
s.db = _FakeDB()
return s
@pytest.mark.asyncio
async def test_mark_pro_seen_returns_none_when_unknown_user_starts_pro():
"""Joining as Pro shouldn't count as a conversion."""
store = _store_with_fake_db()
assert await store.mark_pro_seen("u-new-pro", is_pro=True) is None
@pytest.mark.asyncio
async def test_mark_pro_seen_emits_conversion_after_seeing_user_as_free():
store = _store_with_fake_db()
assert await store.mark_pro_seen("u1", is_pro=False) is None
result = await store.mark_pro_seen("u1", is_pro=True)
assert result is not None
assert result["converted"] is True
assert isinstance(result["first_seen_at"], str)
@pytest.mark.asyncio
async def test_mark_pro_seen_only_fires_conversion_once():
"""Re-checking a converted user must not re-emit the event."""
store = _store_with_fake_db()
await store.mark_pro_seen("u1", is_pro=False)
first = await store.mark_pro_seen("u1", is_pro=True)
assert first is not None and first["converted"] is True
second = await store.mark_pro_seen("u1", is_pro=True)
assert second is None
@pytest.mark.asyncio
async def test_noop_store_mark_pro_seen_returns_none():
store = NoopSessionStore()
assert await store.mark_pro_seen("u1", is_pro=True) is None
assert await store.mark_pro_seen("u1", is_pro=False) is None
# ββ load_usage_events ββββββββββββββββββββββββββββββββββββββββββββββββββββ
class _FakeAsyncCursor:
def __init__(self, docs):
self.docs = docs
self.sort_calls = []
def sort(self, *args, **kwargs):
self.sort_calls.append((args, kwargs))
return self
def __aiter__(self):
self._iter = iter(self.docs)
return self
async def __anext__(self):
try:
return next(self._iter)
except StopIteration:
raise StopAsyncIteration from None
class _FakeFindCollection:
def __init__(self, docs):
self.docs = docs
self.find_calls = []
self.cursors = []
def find(self, query, projection=None):
self.find_calls.append((query, projection))
cursor = _FakeAsyncCursor(self.docs)
self.cursors.append(cursor)
return cursor
class _FakeUsageDB:
def __init__(self, *, sessions, events) -> None:
self.sessions = _FakeFindCollection(sessions)
self.session_events = _FakeFindCollection(events)
def _store_with_fake_usage_db(*, sessions, events) -> MongoSessionStore:
s = MongoSessionStore.__new__(MongoSessionStore)
s.enabled = True
s.db = _FakeUsageDB(sessions=sessions, events=events)
return s
@pytest.mark.asyncio
async def test_load_usage_events_scopes_mongo_queries_to_current_user_and_window():
start = datetime(2026, 6, 1, 0, 0)
end = datetime(2026, 7, 1, 0, 0)
store = _store_with_fake_usage_db(
sessions=[{"_id": "s1"}, {"_id": "s2"}],
events=[
{"session_id": "s1", "event_type": "llm_call", "data": {"cost_usd": 1.0}}
],
)
events = await store.load_usage_events("owner", start=start, end=end)
assert events == [
{"session_id": "s1", "event_type": "llm_call", "data": {"cost_usd": 1.0}}
]
assert store.db.sessions.find_calls == [
(
{"visibility": {"$ne": "deleted"}, "user_id": "owner"},
{"_id": 1},
)
]
assert store.db.session_events.find_calls == [
(
{
"session_id": {"$in": ["s1", "s2"]},
"event_type": {"$in": list(USAGE_EVENT_TYPES)},
"created_at": {"$gte": start, "$lt": end},
},
None,
)
]
assert store.db.session_events.cursors[0].sort_calls == [(("created_at", 1), {})]
@pytest.mark.asyncio
async def test_load_usage_events_dev_mode_uses_requested_session_without_user_filter():
store = _store_with_fake_usage_db(
sessions=[{"_id": "s3"}],
events=[{"session_id": "s3", "event_type": "hf_job_complete", "data": {}}],
)
events = await store.load_usage_events("dev", session_id="s3")
assert events == [{"session_id": "s3", "event_type": "hf_job_complete", "data": {}}]
assert store.db.sessions.find_calls == [
({"visibility": {"$ne": "deleted"}, "_id": "s3"}, {"_id": 1})
]
assert store.db.session_events.find_calls == [
(
{
"session_id": {"$in": ["s3"]},
"event_type": {"$in": list(USAGE_EVENT_TYPES)},
},
None,
)
]
|