roampal 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- roampal/__init__.py +29 -0
- roampal/__main__.py +6 -0
- roampal/backend/__init__.py +1 -0
- roampal/backend/modules/__init__.py +1 -0
- roampal/backend/modules/memory/__init__.py +43 -0
- roampal/backend/modules/memory/chromadb_adapter.py +623 -0
- roampal/backend/modules/memory/config.py +102 -0
- roampal/backend/modules/memory/content_graph.py +543 -0
- roampal/backend/modules/memory/context_service.py +455 -0
- roampal/backend/modules/memory/embedding_service.py +96 -0
- roampal/backend/modules/memory/knowledge_graph_service.py +1052 -0
- roampal/backend/modules/memory/memory_bank_service.py +433 -0
- roampal/backend/modules/memory/memory_types.py +296 -0
- roampal/backend/modules/memory/outcome_service.py +400 -0
- roampal/backend/modules/memory/promotion_service.py +473 -0
- roampal/backend/modules/memory/routing_service.py +444 -0
- roampal/backend/modules/memory/scoring_service.py +324 -0
- roampal/backend/modules/memory/search_service.py +646 -0
- roampal/backend/modules/memory/tests/__init__.py +1 -0
- roampal/backend/modules/memory/tests/conftest.py +12 -0
- roampal/backend/modules/memory/tests/unit/__init__.py +1 -0
- roampal/backend/modules/memory/tests/unit/conftest.py +7 -0
- roampal/backend/modules/memory/tests/unit/test_knowledge_graph_service.py +517 -0
- roampal/backend/modules/memory/tests/unit/test_memory_bank_service.py +504 -0
- roampal/backend/modules/memory/tests/unit/test_outcome_service.py +485 -0
- roampal/backend/modules/memory/tests/unit/test_scoring_service.py +255 -0
- roampal/backend/modules/memory/tests/unit/test_search_service.py +413 -0
- roampal/backend/modules/memory/tests/unit/test_unified_memory_system.py +418 -0
- roampal/backend/modules/memory/unified_memory_system.py +1277 -0
- roampal/cli.py +638 -0
- roampal/hooks/__init__.py +16 -0
- roampal/hooks/session_manager.py +587 -0
- roampal/hooks/stop_hook.py +176 -0
- roampal/hooks/user_prompt_submit_hook.py +103 -0
- roampal/mcp/__init__.py +7 -0
- roampal/mcp/server.py +611 -0
- roampal/server/__init__.py +7 -0
- roampal/server/main.py +744 -0
- roampal-0.1.4.dist-info/METADATA +179 -0
- roampal-0.1.4.dist-info/RECORD +44 -0
- roampal-0.1.4.dist-info/WHEEL +5 -0
- roampal-0.1.4.dist-info/entry_points.txt +2 -0
- roampal-0.1.4.dist-info/licenses/LICENSE +190 -0
- roampal-0.1.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit Tests for MemoryBankService
|
|
3
|
+
|
|
4
|
+
Tests the extracted memory bank operations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', '..')))
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import pytest
|
|
13
|
+
from unittest.mock import MagicMock, AsyncMock
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
|
|
16
|
+
from roampal.backend.modules.memory.memory_bank_service import MemoryBankService
|
|
17
|
+
from roampal.backend.modules.memory.config import MemoryConfig
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestMemoryBankServiceInit:
|
|
21
|
+
"""Test MemoryBankService initialization."""
|
|
22
|
+
|
|
23
|
+
def test_init_with_defaults(self):
|
|
24
|
+
"""Should initialize with default config."""
|
|
25
|
+
collection = MagicMock()
|
|
26
|
+
service = MemoryBankService(
|
|
27
|
+
collection=collection,
|
|
28
|
+
embed_fn=AsyncMock()
|
|
29
|
+
)
|
|
30
|
+
assert service.config is not None
|
|
31
|
+
assert service.MAX_ITEMS == 500
|
|
32
|
+
|
|
33
|
+
def test_init_with_custom_config(self):
|
|
34
|
+
"""Should use custom config."""
|
|
35
|
+
config = MemoryConfig(promotion_score_threshold=0.8)
|
|
36
|
+
service = MemoryBankService(
|
|
37
|
+
collection=MagicMock(),
|
|
38
|
+
embed_fn=AsyncMock(),
|
|
39
|
+
config=config
|
|
40
|
+
)
|
|
41
|
+
assert service.config.promotion_score_threshold == 0.8
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TestStore:
|
|
45
|
+
"""Test memory storage."""
|
|
46
|
+
|
|
47
|
+
@pytest.fixture
|
|
48
|
+
def mock_collection(self):
|
|
49
|
+
coll = MagicMock()
|
|
50
|
+
coll.collection = MagicMock()
|
|
51
|
+
coll.collection.count = MagicMock(return_value=10)
|
|
52
|
+
coll.upsert_vectors = AsyncMock()
|
|
53
|
+
return coll
|
|
54
|
+
|
|
55
|
+
@pytest.fixture
|
|
56
|
+
def service(self, mock_collection):
|
|
57
|
+
return MemoryBankService(
|
|
58
|
+
collection=mock_collection,
|
|
59
|
+
embed_fn=AsyncMock(return_value=[0.1] * 384)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@pytest.mark.asyncio
|
|
63
|
+
async def test_store_basic(self, service, mock_collection):
|
|
64
|
+
"""Should store memory with correct metadata."""
|
|
65
|
+
doc_id = await service.store(
|
|
66
|
+
text="User prefers dark mode",
|
|
67
|
+
tags=["preference"]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
assert doc_id.startswith("memory_bank_")
|
|
71
|
+
mock_collection.upsert_vectors.assert_called_once()
|
|
72
|
+
|
|
73
|
+
call_args = mock_collection.upsert_vectors.call_args
|
|
74
|
+
metadata = call_args[1]["metadatas"][0]
|
|
75
|
+
|
|
76
|
+
assert metadata["text"] == "User prefers dark mode"
|
|
77
|
+
assert metadata["status"] == "active"
|
|
78
|
+
assert metadata["score"] == 1.0
|
|
79
|
+
assert json.loads(metadata["tags"]) == ["preference"]
|
|
80
|
+
|
|
81
|
+
@pytest.mark.asyncio
|
|
82
|
+
async def test_store_with_importance_confidence(self, service, mock_collection):
|
|
83
|
+
"""Should store with custom importance/confidence."""
|
|
84
|
+
await service.store(
|
|
85
|
+
text="Critical info",
|
|
86
|
+
tags=["identity"],
|
|
87
|
+
importance=0.95,
|
|
88
|
+
confidence=0.9
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
call_args = mock_collection.upsert_vectors.call_args
|
|
92
|
+
metadata = call_args[1]["metadatas"][0]
|
|
93
|
+
|
|
94
|
+
assert metadata["importance"] == 0.95
|
|
95
|
+
assert metadata["confidence"] == 0.9
|
|
96
|
+
|
|
97
|
+
@pytest.mark.asyncio
|
|
98
|
+
async def test_store_capacity_check(self, service, mock_collection):
|
|
99
|
+
"""Should reject when at capacity."""
|
|
100
|
+
mock_collection.collection.count = MagicMock(return_value=500)
|
|
101
|
+
|
|
102
|
+
with pytest.raises(ValueError, match="capacity"):
|
|
103
|
+
await service.store("Test", ["test"])
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class TestUpdate:
|
|
107
|
+
"""Test memory update with archiving."""
|
|
108
|
+
|
|
109
|
+
@pytest.fixture
|
|
110
|
+
def mock_collection(self):
|
|
111
|
+
coll = MagicMock()
|
|
112
|
+
coll.get_fragment = MagicMock(return_value={
|
|
113
|
+
"content": "old content",
|
|
114
|
+
"metadata": {
|
|
115
|
+
"text": "old content",
|
|
116
|
+
"tags": '["identity"]',
|
|
117
|
+
"importance": 0.7,
|
|
118
|
+
"confidence": 0.7
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
coll.upsert_vectors = AsyncMock()
|
|
122
|
+
coll.collection = MagicMock()
|
|
123
|
+
coll.collection.count = MagicMock(return_value=10)
|
|
124
|
+
return coll
|
|
125
|
+
|
|
126
|
+
@pytest.fixture
|
|
127
|
+
def service(self, mock_collection):
|
|
128
|
+
return MemoryBankService(
|
|
129
|
+
collection=mock_collection,
|
|
130
|
+
embed_fn=AsyncMock(return_value=[0.1] * 384)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@pytest.mark.asyncio
|
|
134
|
+
async def test_update_archives_old(self, service, mock_collection):
|
|
135
|
+
"""Should archive old version when updating."""
|
|
136
|
+
await service.update(
|
|
137
|
+
doc_id="memory_bank_test123",
|
|
138
|
+
new_text="new content",
|
|
139
|
+
reason="correction"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Should have 2 upsert calls: archive + update
|
|
143
|
+
assert mock_collection.upsert_vectors.call_count == 2
|
|
144
|
+
|
|
145
|
+
# Check archive call
|
|
146
|
+
archive_call = mock_collection.upsert_vectors.call_args_list[0]
|
|
147
|
+
archive_id = archive_call[1]["ids"][0]
|
|
148
|
+
archive_metadata = archive_call[1]["metadatas"][0]
|
|
149
|
+
|
|
150
|
+
assert "archived" in archive_id
|
|
151
|
+
assert archive_metadata["status"] == "archived"
|
|
152
|
+
assert archive_metadata["archive_reason"] == "correction"
|
|
153
|
+
|
|
154
|
+
@pytest.mark.asyncio
|
|
155
|
+
async def test_update_preserves_metadata(self, service, mock_collection):
|
|
156
|
+
"""Should preserve original metadata fields."""
|
|
157
|
+
await service.update(
|
|
158
|
+
doc_id="memory_bank_test123",
|
|
159
|
+
new_text="new content"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
update_call = mock_collection.upsert_vectors.call_args_list[1]
|
|
163
|
+
metadata = update_call[1]["metadatas"][0]
|
|
164
|
+
|
|
165
|
+
assert metadata["importance"] == 0.7
|
|
166
|
+
assert metadata["text"] == "new content"
|
|
167
|
+
|
|
168
|
+
@pytest.mark.asyncio
|
|
169
|
+
async def test_update_not_found_creates_new(self, service, mock_collection):
|
|
170
|
+
"""Should create new memory if not found."""
|
|
171
|
+
mock_collection.get_fragment = MagicMock(return_value=None)
|
|
172
|
+
|
|
173
|
+
doc_id = await service.update(
|
|
174
|
+
doc_id="memory_bank_nonexistent",
|
|
175
|
+
new_text="new memory"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
assert doc_id.startswith("memory_bank_")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class TestArchive:
|
|
182
|
+
"""Test memory archiving."""
|
|
183
|
+
|
|
184
|
+
@pytest.fixture
|
|
185
|
+
def mock_collection(self):
|
|
186
|
+
coll = MagicMock()
|
|
187
|
+
coll.get_fragment = MagicMock(return_value={
|
|
188
|
+
"content": "test",
|
|
189
|
+
"metadata": {"status": "active"}
|
|
190
|
+
})
|
|
191
|
+
coll.update_fragment_metadata = MagicMock()
|
|
192
|
+
return coll
|
|
193
|
+
|
|
194
|
+
@pytest.fixture
|
|
195
|
+
def service(self, mock_collection):
|
|
196
|
+
return MemoryBankService(
|
|
197
|
+
collection=mock_collection,
|
|
198
|
+
embed_fn=AsyncMock()
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
@pytest.mark.asyncio
|
|
202
|
+
async def test_archive_success(self, service, mock_collection):
|
|
203
|
+
"""Should archive memory successfully."""
|
|
204
|
+
result = await service.archive(
|
|
205
|
+
doc_id="memory_bank_test123",
|
|
206
|
+
reason="outdated"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
assert result is True
|
|
210
|
+
mock_collection.update_fragment_metadata.assert_called_once()
|
|
211
|
+
|
|
212
|
+
call_args = mock_collection.update_fragment_metadata.call_args
|
|
213
|
+
metadata = call_args[0][1]
|
|
214
|
+
assert metadata["status"] == "archived"
|
|
215
|
+
assert metadata["archive_reason"] == "outdated"
|
|
216
|
+
|
|
217
|
+
@pytest.mark.asyncio
|
|
218
|
+
async def test_archive_not_found(self, service, mock_collection):
|
|
219
|
+
"""Should return False if not found."""
|
|
220
|
+
mock_collection.get_fragment = MagicMock(return_value=None)
|
|
221
|
+
|
|
222
|
+
result = await service.archive("nonexistent")
|
|
223
|
+
assert result is False
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class TestSearch:
|
|
227
|
+
"""Test memory search."""
|
|
228
|
+
|
|
229
|
+
@pytest.fixture
|
|
230
|
+
def mock_collection(self):
|
|
231
|
+
coll = MagicMock()
|
|
232
|
+
coll.list_all_ids = MagicMock(return_value=[
|
|
233
|
+
"memory_bank_1", "memory_bank_2", "memory_bank_3"
|
|
234
|
+
])
|
|
235
|
+
|
|
236
|
+
def get_fragment_side_effect(doc_id):
|
|
237
|
+
if doc_id == "memory_bank_1":
|
|
238
|
+
return {
|
|
239
|
+
"content": "User name is John",
|
|
240
|
+
"metadata": {"status": "active", "tags": '["identity"]'}
|
|
241
|
+
}
|
|
242
|
+
elif doc_id == "memory_bank_2":
|
|
243
|
+
return {
|
|
244
|
+
"content": "Prefers dark mode",
|
|
245
|
+
"metadata": {"status": "active", "tags": '["preference"]'}
|
|
246
|
+
}
|
|
247
|
+
elif doc_id == "memory_bank_3":
|
|
248
|
+
return {
|
|
249
|
+
"content": "Old info",
|
|
250
|
+
"metadata": {"status": "archived", "tags": '["identity"]'}
|
|
251
|
+
}
|
|
252
|
+
return None
|
|
253
|
+
|
|
254
|
+
coll.get_fragment = MagicMock(side_effect=get_fragment_side_effect)
|
|
255
|
+
return coll
|
|
256
|
+
|
|
257
|
+
@pytest.fixture
|
|
258
|
+
def service(self, mock_collection):
|
|
259
|
+
return MemoryBankService(
|
|
260
|
+
collection=mock_collection,
|
|
261
|
+
embed_fn=AsyncMock()
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
@pytest.mark.asyncio
|
|
265
|
+
async def test_search_excludes_archived_by_default(self, service):
|
|
266
|
+
"""Should exclude archived memories by default."""
|
|
267
|
+
results = await service.search()
|
|
268
|
+
assert len(results) == 2
|
|
269
|
+
|
|
270
|
+
@pytest.mark.asyncio
|
|
271
|
+
async def test_search_includes_archived_when_requested(self, service):
|
|
272
|
+
"""Should include archived when requested."""
|
|
273
|
+
results = await service.search(include_archived=True)
|
|
274
|
+
assert len(results) == 3
|
|
275
|
+
|
|
276
|
+
@pytest.mark.asyncio
|
|
277
|
+
async def test_search_filters_by_tags(self, service):
|
|
278
|
+
"""Should filter by tags."""
|
|
279
|
+
results = await service.search(tags=["identity"])
|
|
280
|
+
assert len(results) == 1
|
|
281
|
+
assert results[0]["metadata"]["tags"] == '["identity"]'
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class TestRestore:
|
|
285
|
+
"""Test memory restoration."""
|
|
286
|
+
|
|
287
|
+
@pytest.fixture
|
|
288
|
+
def mock_collection(self):
|
|
289
|
+
coll = MagicMock()
|
|
290
|
+
coll.get_fragment = MagicMock(return_value={
|
|
291
|
+
"content": "test",
|
|
292
|
+
"metadata": {"status": "archived"}
|
|
293
|
+
})
|
|
294
|
+
coll.update_fragment_metadata = MagicMock()
|
|
295
|
+
return coll
|
|
296
|
+
|
|
297
|
+
@pytest.fixture
|
|
298
|
+
def service(self, mock_collection):
|
|
299
|
+
return MemoryBankService(
|
|
300
|
+
collection=mock_collection,
|
|
301
|
+
embed_fn=AsyncMock()
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
@pytest.mark.asyncio
|
|
305
|
+
async def test_restore_success(self, service, mock_collection):
|
|
306
|
+
"""Should restore archived memory."""
|
|
307
|
+
result = await service.restore("memory_bank_test123")
|
|
308
|
+
|
|
309
|
+
assert result is True
|
|
310
|
+
call_args = mock_collection.update_fragment_metadata.call_args
|
|
311
|
+
metadata = call_args[0][1]
|
|
312
|
+
assert metadata["status"] == "active"
|
|
313
|
+
assert metadata["restored_by"] == "user"
|
|
314
|
+
|
|
315
|
+
@pytest.mark.asyncio
|
|
316
|
+
async def test_restore_not_found(self, service, mock_collection):
|
|
317
|
+
"""Should return False if not found."""
|
|
318
|
+
mock_collection.get_fragment = MagicMock(return_value=None)
|
|
319
|
+
|
|
320
|
+
result = await service.restore("nonexistent")
|
|
321
|
+
assert result is False
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class TestDelete:
|
|
325
|
+
"""Test memory deletion."""
|
|
326
|
+
|
|
327
|
+
@pytest.fixture
|
|
328
|
+
def mock_collection(self):
|
|
329
|
+
coll = MagicMock()
|
|
330
|
+
coll.delete_vectors = MagicMock()
|
|
331
|
+
return coll
|
|
332
|
+
|
|
333
|
+
@pytest.fixture
|
|
334
|
+
def service(self, mock_collection):
|
|
335
|
+
return MemoryBankService(
|
|
336
|
+
collection=mock_collection,
|
|
337
|
+
embed_fn=AsyncMock()
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
@pytest.mark.asyncio
|
|
341
|
+
async def test_delete_success(self, service, mock_collection):
|
|
342
|
+
"""Should delete memory successfully."""
|
|
343
|
+
result = await service.delete("memory_bank_test123")
|
|
344
|
+
|
|
345
|
+
assert result is True
|
|
346
|
+
mock_collection.delete_vectors.assert_called_with(["memory_bank_test123"])
|
|
347
|
+
|
|
348
|
+
@pytest.mark.asyncio
|
|
349
|
+
async def test_delete_failure(self, service, mock_collection):
|
|
350
|
+
"""Should return False on error."""
|
|
351
|
+
mock_collection.delete_vectors = MagicMock(side_effect=Exception("Test error"))
|
|
352
|
+
|
|
353
|
+
result = await service.delete("memory_bank_test123")
|
|
354
|
+
assert result is False
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
class TestListAll:
|
|
358
|
+
"""Test listing all memories."""
|
|
359
|
+
|
|
360
|
+
@pytest.fixture
|
|
361
|
+
def mock_collection(self):
|
|
362
|
+
coll = MagicMock()
|
|
363
|
+
coll.list_all_ids = MagicMock(return_value=["m1", "m2", "m3"])
|
|
364
|
+
|
|
365
|
+
def get_fragment_side_effect(doc_id):
|
|
366
|
+
return {
|
|
367
|
+
"content": f"content_{doc_id}",
|
|
368
|
+
"metadata": {
|
|
369
|
+
"status": "active" if doc_id != "m3" else "archived",
|
|
370
|
+
"tags": '["identity"]' if doc_id == "m1" else '["preference"]'
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
coll.get_fragment = MagicMock(side_effect=get_fragment_side_effect)
|
|
375
|
+
return coll
|
|
376
|
+
|
|
377
|
+
@pytest.fixture
|
|
378
|
+
def service(self, mock_collection):
|
|
379
|
+
return MemoryBankService(
|
|
380
|
+
collection=mock_collection,
|
|
381
|
+
embed_fn=AsyncMock()
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def test_list_all_excludes_archived(self, service):
|
|
385
|
+
"""Should exclude archived by default."""
|
|
386
|
+
results = service.list_all()
|
|
387
|
+
assert len(results) == 2
|
|
388
|
+
|
|
389
|
+
def test_list_all_includes_archived(self, service):
|
|
390
|
+
"""Should include archived when requested."""
|
|
391
|
+
results = service.list_all(include_archived=True)
|
|
392
|
+
assert len(results) == 3
|
|
393
|
+
|
|
394
|
+
def test_list_all_filters_tags(self, service):
|
|
395
|
+
"""Should filter by tags."""
|
|
396
|
+
results = service.list_all(tags=["identity"])
|
|
397
|
+
assert len(results) == 1
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class TestStats:
|
|
401
|
+
"""Test statistics retrieval."""
|
|
402
|
+
|
|
403
|
+
@pytest.fixture
|
|
404
|
+
def mock_collection(self):
|
|
405
|
+
coll = MagicMock()
|
|
406
|
+
coll.list_all_ids = MagicMock(return_value=["m1", "m2", "m3"])
|
|
407
|
+
|
|
408
|
+
def get_fragment_side_effect(doc_id):
|
|
409
|
+
if doc_id == "m1":
|
|
410
|
+
return {
|
|
411
|
+
"content": "identity",
|
|
412
|
+
"metadata": {
|
|
413
|
+
"status": "active",
|
|
414
|
+
"tags": '["identity"]',
|
|
415
|
+
"importance": 0.9,
|
|
416
|
+
"confidence": 0.8
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
elif doc_id == "m2":
|
|
420
|
+
return {
|
|
421
|
+
"content": "preference",
|
|
422
|
+
"metadata": {
|
|
423
|
+
"status": "active",
|
|
424
|
+
"tags": '["preference", "identity"]',
|
|
425
|
+
"importance": 0.7,
|
|
426
|
+
"confidence": 0.7
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
elif doc_id == "m3":
|
|
430
|
+
return {
|
|
431
|
+
"content": "archived",
|
|
432
|
+
"metadata": {
|
|
433
|
+
"status": "archived",
|
|
434
|
+
"tags": '["old"]',
|
|
435
|
+
"importance": 0.5,
|
|
436
|
+
"confidence": 0.5
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
coll.get_fragment = MagicMock(side_effect=get_fragment_side_effect)
|
|
442
|
+
return coll
|
|
443
|
+
|
|
444
|
+
@pytest.fixture
|
|
445
|
+
def service(self, mock_collection):
|
|
446
|
+
return MemoryBankService(
|
|
447
|
+
collection=mock_collection,
|
|
448
|
+
embed_fn=AsyncMock()
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
def test_get_stats(self, service):
|
|
452
|
+
"""Should return correct statistics."""
|
|
453
|
+
stats = service.get_stats()
|
|
454
|
+
|
|
455
|
+
assert stats["total"] == 3
|
|
456
|
+
assert stats["active"] == 2
|
|
457
|
+
assert stats["archived"] == 1
|
|
458
|
+
assert stats["capacity"] == 500
|
|
459
|
+
assert stats["tag_counts"]["identity"] == 2
|
|
460
|
+
assert stats["tag_counts"]["preference"] == 1
|
|
461
|
+
assert stats["avg_importance"] == 0.8 # (0.9 + 0.7) / 2
|
|
462
|
+
assert stats["avg_confidence"] == 0.75 # (0.8 + 0.7) / 2
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
class TestIncrementMention:
|
|
466
|
+
"""Test mention count tracking."""
|
|
467
|
+
|
|
468
|
+
@pytest.fixture
|
|
469
|
+
def mock_collection(self):
|
|
470
|
+
coll = MagicMock()
|
|
471
|
+
coll.get_fragment = MagicMock(return_value={
|
|
472
|
+
"content": "test",
|
|
473
|
+
"metadata": {"mentioned_count": 5}
|
|
474
|
+
})
|
|
475
|
+
coll.update_fragment_metadata = MagicMock()
|
|
476
|
+
return coll
|
|
477
|
+
|
|
478
|
+
@pytest.fixture
|
|
479
|
+
def service(self, mock_collection):
|
|
480
|
+
return MemoryBankService(
|
|
481
|
+
collection=mock_collection,
|
|
482
|
+
embed_fn=AsyncMock()
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
def test_increment_mention(self, service, mock_collection):
|
|
486
|
+
"""Should increment mention count."""
|
|
487
|
+
result = service.increment_mention("memory_bank_test123")
|
|
488
|
+
|
|
489
|
+
assert result is True
|
|
490
|
+
call_args = mock_collection.update_fragment_metadata.call_args
|
|
491
|
+
metadata = call_args[0][1]
|
|
492
|
+
assert metadata["mentioned_count"] == 6
|
|
493
|
+
assert "last_mentioned" in metadata
|
|
494
|
+
|
|
495
|
+
def test_increment_not_found(self, service, mock_collection):
|
|
496
|
+
"""Should return False if not found."""
|
|
497
|
+
mock_collection.get_fragment = MagicMock(return_value=None)
|
|
498
|
+
|
|
499
|
+
result = service.increment_mention("nonexistent")
|
|
500
|
+
assert result is False
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
if __name__ == "__main__":
|
|
504
|
+
pytest.main([__file__, "-v"])
|