From 399ed8b5df771ff8e63d9a3d5fd184875b2df4ca Mon Sep 17 00:00:00 2001 From: curo1305 Date: Mon, 18 May 2026 15:23:57 +0200 Subject: [PATCH] test: add memory database tests and update conftest for DB isolation conftest patches mdb._DB_PATH and calls init_db() after directory creation so all existing tests continue to work with the new DB layer. New test_memory_db.py covers upsert, search, remove, migration, and the updated list_memories/lookup_memories integration paths. Co-Authored-By: Claude Sonnet 4.6 --- tests/conftest.py | 5 ++ tests/unit/test_memory_db.py | 119 +++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 tests/unit/test_memory_db.py diff --git a/tests/conftest.py b/tests/conftest.py index 3b7ecfe..fe3bfc0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,6 +28,7 @@ def tmp_pyra_home(tmp_path, monkeypatch): import pyra.plugins.loader as pl import pyra.plugins.executor as pe + import pyra.memory.database as mdb b.VAULT_PATH = fake_home / "vault" b.BLOCKED_PREFIXES = [b.VAULT_PATH] @@ -35,6 +36,8 @@ def tmp_pyra_home(tmp_path, monkeypatch): mi._INDEX_FILE = fake_home / "memory" / "MEMORY_INDEX.md" mr._MEMORY_ROOT = fake_home / "memory" mw._MEMORY_ROOT = fake_home / "memory" + mdb._DB_PATH = fake_home / "memory" / "memory.db" + mdb._MEMORY_ROOT = fake_home / "memory" vr._KEYS_FILE = fake_home / "vault" / "secrets" / "api_keys.json" vw._KEYS_FILE = fake_home / "vault" / "secrets" / "api_keys.json" si._LOG_FILE = fake_home / "security.log" @@ -52,6 +55,8 @@ def tmp_pyra_home(tmp_path, monkeypatch): (fake_home / "plugins").mkdir() (fake_home / "logs").mkdir() + mdb.init_db() + # Reset plugin registry singleton so tests don't share state from pyra.plugins.registry import PluginRegistry PluginRegistry.reset() diff --git a/tests/unit/test_memory_db.py b/tests/unit/test_memory_db.py new file mode 100644 index 0000000..0f8f6ba --- /dev/null +++ b/tests/unit/test_memory_db.py @@ -0,0 +1,119 @@ +import json + + +def test_init_creates_db(tmp_pyra_home): + from pyra.memory import database + assert database._DB_PATH.exists() + + +def test_upsert_and_list(tmp_pyra_home): + from pyra.memory import database + database.upsert( + "user/profile.md", + content="# Profile\n\nI am a developer.", + category="user", + size_bytes=30, + modified="2026-05-18T10:00:00", + summary="Developer profile", + keywords=["developer", "profile"], + ) + rows = database.list_all() + assert len(rows) == 1 + row = rows[0] + assert row["path"] == "user/profile.md" + assert row["category"] == "user" + assert row["summary"] == "Developer profile" + assert row["keywords"] == ["developer", "profile"] + + +def test_upsert_overwrites(tmp_pyra_home): + from pyra.memory import database + database.upsert("context/notes.md", content="old", category="context", + modified="2026-05-18T10:00:00") + database.upsert("context/notes.md", content="new", category="context", + summary="updated", modified="2026-05-18T11:00:00") + rows = database.list_all() + assert len(rows) == 1 + assert rows[0]["summary"] == "updated" + + +def test_remove(tmp_pyra_home): + from pyra.memory import database + database.upsert("knowledge/facts.md", content="Some facts.", category="knowledge", + modified="2026-05-18T10:00:00") + assert len(database.list_all()) == 1 + database.remove("knowledge/facts.md") + assert len(database.list_all()) == 0 + + +def test_search_fts(tmp_pyra_home): + from pyra.memory import database + database.upsert("user/profile.md", content="I enjoy building AI tools.", + category="user", modified="2026-05-18T10:00:00", + summary="Personal bio", keywords=["AI", "tools"]) + database.upsert("knowledge/cooking.md", content="Pasta recipes and techniques.", + category="knowledge", modified="2026-05-18T10:00:00", + summary="Cooking notes", keywords=["pasta", "cooking"]) + results = database.search("AI tools") + assert len(results) == 1 + assert results[0]["file"] == "user/profile.md" + assert results[0]["summary"] == "Personal bio" + assert "AI" in results[0]["keywords"] + + +def test_search_no_match(tmp_pyra_home): + from pyra.memory import database + database.upsert("user/profile.md", content="Hello world.", category="user", + modified="2026-05-18T10:00:00") + results = database.search("xyzzy") + assert results == [] + + +def test_search_invalid_query_returns_empty(tmp_pyra_home): + from pyra.memory import database + database.upsert("user/profile.md", content="Hello world.", category="user", + modified="2026-05-18T10:00:00") + # FTS5 special chars that could raise OperationalError are handled gracefully + results = database.search('"unclosed quote') + assert isinstance(results, list) + + +def test_migrate_from_files(tmp_pyra_home): + from pyra.memory.writer import write_memory + from pyra.memory import database + + write_memory("user/note.md", "Migration test content.") + + # Wipe DB to simulate fresh state before migration + database.remove("user/note.md") + assert database.list_all() == [] + + # Manually call migrate — it should re-populate from the .md file + database.migrate_from_files() + rows = database.list_all() + assert any(r["path"] == "user/note.md" for r in rows) + + +def test_list_memories_uses_db(tmp_pyra_home): + from pyra.memory.writer import write_memory + from pyra.memory.reader import list_memories + + write_memory("context/project.md", "# Project\n\nActive tasks.") + memories = list_memories() + names = [m.name for m in memories] + assert "context/project.md" in names + + +def test_lookup_memories_uses_fts(tmp_pyra_home): + from pyra.memory.writer import write_memory + from pyra.memory.reader import lookup_memories + + write_memory( + "knowledge/python.md", + "Python is a high-level programming language.", + summary="Python overview", + keywords=["python", "programming"], + ) + results = lookup_memories("programming language") + assert len(results) >= 1 + assert results[0]["file"] == "knowledge/python.md"