summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorbatsumaru <>2026-09-15 14:25:49 +0900
committerbatsumaru <>2026-09-15 14:25:49 +0900
commit5629cabbb38c7d91e66a419ba7cb3a93a9a988a1 (patch)
treef72fc35376dc9bce014bc515ea7672c85782ed14 /tests
parent606e264c9c6ba99ae0c04a8463d37d85568f0c86 (diff)
Added long-term memory
Diffstat (limited to 'tests')
-rw-r--r--tests/test_database.py89
-rw-r--r--tests/test_memory_store.py213
2 files changed, 302 insertions, 0 deletions
diff --git a/tests/test_database.py b/tests/test_database.py
new file mode 100644
index 0000000..e33fdae
--- /dev/null
+++ b/tests/test_database.py
@@ -0,0 +1,89 @@
+import sqlite3
+
+import pytest
+
+from database.database import Database
+
+
+def test_migrate_creates_schema(tmp_path):
+ database = Database(tmp_path / "test.db")
+
+ database.migrate()
+
+ with database.connect() as connection:
+ tables = {
+ row[0]
+ for row in connection.execute(
+ """
+ SELECT name
+ FROM sqlite_master
+ WHERE type = 'table'
+ """
+ )
+ }
+
+ assert "schema_migrations" in tables
+ assert "memories" in tables
+
+
+def test_migrate_records_version(tmp_path):
+ database = Database(tmp_path / "test.db")
+
+ database.migrate()
+
+ with database.connect() as connection:
+ rows = connection.execute("SELECT version FROM schema_migrations").fetchall()
+
+ assert rows == [(1,)]
+
+
+def test_migrate_is_idempotent(tmp_path):
+ database = Database(tmp_path / "test.db")
+
+ database.migrate()
+ database.migrate()
+
+ with database.connect() as connection:
+ rows = connection.execute("SELECT version FROM schema_migrations").fetchall()
+
+ assert rows == [(1,)]
+
+
+def test_failed_migration_rolls_back(tmp_path):
+ database = Database(tmp_path / "test.db")
+
+ migration = database.migrations_dir / "002_failing_migration.sql"
+ migration.write_text(
+ """
+ THIS IS INVALID SQL;
+ """,
+ encoding="utf-8",
+ )
+ try:
+ with pytest.raises(sqlite3.OperationalError):
+ database.migrate()
+
+ with database.connect() as connection:
+ migrations = connection.execute(
+ "SELECT version FROM schema_migrations"
+ ).fetchall()
+
+ assert migrations == [(1,)]
+ finally:
+ migration.unlink()
+
+
+def test_duplicate_migration_versions_are_rejected(tmp_path):
+ database = Database(tmp_path / "test.db")
+
+ duplicate = database.migrations_dir / "001_duplicate.sql"
+ duplicate.write_text(
+ "CREATE TABLE duplicate_table (id INTEGER PRIMARY KEY);",
+ encoding="utf-8",
+ )
+
+ try:
+ with pytest.raises(ValueError, match="Duplicate migration version"):
+ database.migrate()
+ finally:
+ duplicate.unlink()
diff --git a/tests/test_memory_store.py b/tests/test_memory_store.py
new file mode 100644
index 0000000..f4862cb
--- /dev/null
+++ b/tests/test_memory_store.py
@@ -0,0 +1,213 @@
+from datetime import UTC, datetime
+from uuid import UUID, uuid7
+
+from database.database import Database
+from memory import LongTermMemory, MemoryCategory
+from memory_store import LongTermMemoryStore
+
+
+def create_store(tmp_path):
+ database = Database(tmp_path / "test.db")
+ database.migrate()
+
+ return LongTermMemoryStore(database)
+
+
+def test_get_returns_memory(tmp_path):
+ store = create_store(tmp_path)
+
+ memory = LongTermMemory.create(
+ "The user prefers direct technical explanations.",
+ MemoryCategory.SEMANTIC,
+ )
+
+ store.add(memory)
+
+ assert store.get(memory.id) == memory
+
+
+def test_created_memory_has_uuid7_id():
+ memory = LongTermMemory.create(
+ "UUIDv7 teset",
+ MemoryCategory.SEMANTIC,
+ )
+
+ assert memory.id.version == 7
+
+
+def test_created_memories_have_unique_ids():
+ first = LongTermMemory.create(
+ "First memory.",
+ MemoryCategory.SEMANTIC,
+ )
+ second = LongTermMemory.create(
+ "Second memory.",
+ MemoryCategory.SEMANTIC,
+ )
+
+ assert first.id != second.id
+
+
+def test_get_preserves_memory_id(tmp_path):
+ store = create_store(tmp_path)
+
+ memory = LongTermMemory.create(
+ "ID round-trip test.",
+ MemoryCategory.SEMANTIC,
+ )
+
+ store.add(memory)
+
+ result = store.get(memory.id)
+
+ assert result is not None
+ assert result.id == memory.id
+
+
+def test_get_missing_memory_returns_none(tmp_path):
+ store = create_store(tmp_path)
+
+ assert store.get(UUID("00000000-0000-7a3f-9c2e-1b4d8f6a0e37")) is None
+
+
+def test_list_returns_memories(tmp_path):
+ store = create_store(tmp_path)
+
+ first = LongTermMemory.create(
+ "The user likes systems programming.",
+ MemoryCategory.SEMANTIC,
+ )
+ second = LongTermMemory.create(
+ "The user completed Phase 7.1a.",
+ MemoryCategory.EPISODIC,
+ )
+
+ store.add(first)
+ store.add(second)
+
+ assert store.list() == [first, second]
+
+
+def test_delete_removes_memory(tmp_path):
+ store = create_store(tmp_path)
+
+ memory = LongTermMemory.create(
+ "Temporary memory.",
+ MemoryCategory.EPISODIC,
+ )
+
+ store.add(memory)
+
+ store.delete(memory.id)
+
+ assert store.get(memory.id) is None
+
+
+def test_created_at_round_trips_as_utc(tmp_path):
+ store = create_store(tmp_path)
+
+ created_at = datetime(
+ 2026,
+ 9,
+ 15,
+ 12,
+ 34,
+ 56,
+ 123456,
+ tzinfo=UTC,
+ )
+
+ memory = LongTermMemory(
+ id=uuid7(),
+ content="UTC timestamp test.",
+ category=MemoryCategory.SEMANTIC,
+ created_at=created_at,
+ )
+
+ store.add(memory)
+ result = store.get(memory.id)
+
+ assert result is not None
+ assert result.created_at == created_at
+ assert result.created_at.tzinfo is not None
+ assert result.created_at.utcoffset() == UTC.utcoffset(None)
+
+
+def test_search_matches_content(tmp_path):
+ store = create_store(tmp_path)
+
+ first = LongTermMemory.create(
+ "The user prefers direct technical explanations.",
+ MemoryCategory.SEMANTIC,
+ )
+ second = LongTermMemory.create(
+ "The user is working on a local personal agent.",
+ MemoryCategory.SEMANTIC,
+ )
+
+ store.add(first)
+ store.add(second)
+
+ assert store.search("technical") == [first]
+
+
+def test_search_returns_empty_list_when_no_memory_matches(tmp_path):
+ store = create_store(tmp_path)
+
+ memory = LongTermMemory.create(
+ "The user likes systems programming.",
+ MemoryCategory.SEMANTIC,
+ )
+
+ store.add(memory)
+
+ assert store.search("quantum physics") == []
+
+
+def test_search_is_case_insensitive(tmp_path):
+ store = create_store(tmp_path)
+
+ memory = LongTermMemory.create(
+ "The user prefers Rust.",
+ MemoryCategory.SEMANTIC,
+ )
+
+ store.add(memory)
+
+ assert store.search("RUST") == [memory]
+
+
+def test_search_can_filter_by_category(tmp_path):
+ store = create_store(tmp_path)
+
+ semantic = LongTermMemory.create(
+ "The user likes programming.",
+ MemoryCategory.SEMANTIC,
+ )
+ episodic = LongTermMemory.create(
+ "The user discussed programming yesterday.",
+ MemoryCategory.EPISODIC,
+ )
+
+ store.add(semantic)
+ store.add(episodic)
+
+ assert store.search("programming", MemoryCategory.SEMANTIC) == [semantic]
+ assert store.search("programming", MemoryCategory.EPISODIC) == [episodic]
+
+
+def test_search_respects_limit(tmp_path):
+ store = create_store(tmp_path)
+
+ memories = [
+ LongTermMemory.create(
+ f"Programming memory {index}.",
+ MemoryCategory.SEMANTIC,
+ )
+ for index in range(3)
+ ]
+
+ for memory in memories:
+ store.add(memory)
+
+ assert store.search("programming", limit=2) == memories[:2]