mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 14:07:55 +00:00
Closes #582. Route fact-store construction through a new FactStoreRegistry (local backend registered by default); align the default facts path with get_config_dir(); wire completed chat exchanges (streamed and non-streamed) through the EventBus so the memory service captures them consistently; reload the local fact store from disk before operations so external clears don't resurrect stale facts; make the affected config/persona/memory/CLI/route tests hermetic; and refresh uv.lock with the current resolver (locks pytest-xdist + transitive deps, drops py3.14 artifacts since the project constrains Python <3.14). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
148 lines
4.2 KiB
Python
148 lines
4.2 KiB
Python
"""Tests for the persistent fact store (openjarvis.memory.store)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from openjarvis.core.registry import FactStoreRegistry
|
|
from openjarvis.memory.store import LocalFactStore, create_fact_store
|
|
|
|
|
|
def test_add_and_list(tmp_path):
|
|
store = LocalFactStore(tmp_path / "facts.jsonl")
|
|
assert store.add("User prefers concise answers") is True
|
|
assert store.add("User lives in Berlin") is True
|
|
|
|
facts = store.list()
|
|
assert [f.text for f in facts] == [
|
|
"User prefers concise answers",
|
|
"User lives in Berlin",
|
|
]
|
|
assert store.count() == 2
|
|
|
|
|
|
def test_add_dedupes_case_insensitive(tmp_path):
|
|
store = LocalFactStore(tmp_path / "facts.jsonl")
|
|
assert store.add("Likes coffee") is True
|
|
assert store.add("likes coffee") is False # duplicate
|
|
assert store.count() == 1
|
|
|
|
|
|
def test_add_skips_empty(tmp_path):
|
|
store = LocalFactStore(tmp_path / "facts.jsonl")
|
|
assert store.add("") is False
|
|
assert store.add(" ") is False
|
|
assert store.count() == 0
|
|
|
|
|
|
def test_add_many(tmp_path):
|
|
store = LocalFactStore(tmp_path / "facts.jsonl")
|
|
added = store.add_many(["a", "b", "a", "c"]) # one dupe
|
|
assert added == 3
|
|
assert store.count() == 3
|
|
|
|
|
|
def test_max_facts_evicts_oldest(tmp_path):
|
|
store = LocalFactStore(tmp_path / "facts.jsonl", max_facts=2)
|
|
store.add("first")
|
|
store.add("second")
|
|
store.add("third")
|
|
facts = [f.text for f in store.list()]
|
|
assert facts == ["second", "third"] # oldest dropped
|
|
|
|
|
|
def test_persistence_across_instances(tmp_path):
|
|
path = tmp_path / "facts.jsonl"
|
|
store = LocalFactStore(path)
|
|
store.add("durable fact")
|
|
|
|
reloaded = LocalFactStore(path)
|
|
assert [f.text for f in reloaded.list()] == ["durable fact"]
|
|
|
|
|
|
def test_clear(tmp_path):
|
|
path = tmp_path / "facts.jsonl"
|
|
store = LocalFactStore(path)
|
|
store.add("one")
|
|
store.add("two")
|
|
|
|
removed = store.clear()
|
|
assert removed == 2
|
|
assert store.count() == 0
|
|
# A fresh instance also sees an empty store.
|
|
assert LocalFactStore(path).count() == 0
|
|
|
|
|
|
def test_external_clear_does_not_resurrect_stale_facts(tmp_path):
|
|
"""A running store instance must not re-flush facts cleared elsewhere."""
|
|
path = tmp_path / "facts.jsonl"
|
|
running = LocalFactStore(path)
|
|
cli = LocalFactStore(path)
|
|
|
|
running.add("old fact")
|
|
assert cli.clear() == 1
|
|
|
|
running.add("new fact")
|
|
|
|
assert [f.text for f in LocalFactStore(path).list()] == ["new fact"]
|
|
|
|
|
|
def test_load_skips_malformed_lines(tmp_path):
|
|
path = tmp_path / "facts.jsonl"
|
|
path.write_text(
|
|
'{"text": "good fact"}\n'
|
|
"this is not json\n"
|
|
'{"text": ""}\n' # empty text ignored
|
|
'{"text": "another good"}\n',
|
|
encoding="utf-8",
|
|
)
|
|
store = LocalFactStore(path)
|
|
assert [f.text for f in store.list()] == ["good fact", "another good"]
|
|
|
|
|
|
def test_jsonl_round_trip_is_valid_json(tmp_path):
|
|
path = tmp_path / "facts.jsonl"
|
|
store = LocalFactStore(path)
|
|
store.add("fact one", source="auto")
|
|
|
|
lines = [
|
|
line for line in path.read_text(encoding="utf-8").splitlines() if line.strip()
|
|
]
|
|
assert len(lines) == 1
|
|
obj = json.loads(lines[0])
|
|
assert obj["text"] == "fact one"
|
|
assert obj["source"] == "auto"
|
|
assert "created_at" in obj
|
|
|
|
|
|
def test_create_fact_store_local(tmp_path):
|
|
store = create_fact_store("local", path=tmp_path / "f.jsonl", max_facts=5)
|
|
assert isinstance(store, LocalFactStore)
|
|
|
|
|
|
def test_create_fact_store_uses_fact_store_registry(tmp_path):
|
|
class CustomFactStore(LocalFactStore):
|
|
pass
|
|
|
|
FactStoreRegistry.register_value("custom", CustomFactStore)
|
|
|
|
store = create_fact_store("custom", path=tmp_path / "f.jsonl", max_facts=5)
|
|
|
|
assert isinstance(store, CustomFactStore)
|
|
|
|
|
|
def test_create_fact_store_default_path_uses_openjarvis_home(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("OPENJARVIS_HOME", str(tmp_path))
|
|
|
|
store = create_fact_store("local")
|
|
|
|
assert isinstance(store, LocalFactStore)
|
|
assert store.path == tmp_path / "memory_facts.jsonl"
|
|
|
|
|
|
def test_create_fact_store_unknown_backend(tmp_path):
|
|
with pytest.raises(ValueError):
|
|
create_fact_store("cloud", path=tmp_path / "f.jsonl")
|