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,
        )
    ]