212 lines
8.6 KiB
Python
212 lines
8.6 KiB
Python
"""Tests for Mnemosyne archive core."""
|
|
|
|
import json
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from nexus.mnemosyne.entry import ArchiveEntry
|
|
from nexus.mnemosyne.linker import HolographicLinker
|
|
from nexus.mnemosyne.archive import MnemosyneArchive
|
|
from nexus.mnemosyne.ingest import ingest_event, ingest_from_mempalace
|
|
|
|
|
|
def test_entry_roundtrip():
|
|
e = ArchiveEntry(title="Test", content="Hello world", topics=["test"])
|
|
d = e.to_dict()
|
|
e2 = ArchiveEntry.from_dict(d)
|
|
assert e2.id == e.id
|
|
assert e2.title == "Test"
|
|
|
|
|
|
def test_linker_similarity():
|
|
linker = HolographicLinker()
|
|
a = ArchiveEntry(title="Python coding", content="Writing Python scripts for automation")
|
|
b = ArchiveEntry(title="Python scripting", content="Automating tasks with Python scripts")
|
|
c = ArchiveEntry(title="Cooking recipes", content="How to make pasta carbonara")
|
|
assert linker.compute_similarity(a, b) > linker.compute_similarity(a, c)
|
|
|
|
|
|
def test_archive_add_and_search():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
ingest_event(archive, title="First entry", content="Hello archive", topics=["test"])
|
|
ingest_event(archive, title="Second entry", content="Another record", topics=["test", "demo"])
|
|
assert archive.count == 2
|
|
results = archive.search("hello")
|
|
assert len(results) == 1
|
|
assert results[0].title == "First entry"
|
|
|
|
|
|
def test_archive_auto_linking():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
e1 = ingest_event(archive, title="Python automation", content="Building automation tools in Python")
|
|
e2 = ingest_event(archive, title="Python scripting", content="Writing automation scripts using Python")
|
|
# Both should be linked due to shared tokens
|
|
assert len(e1.links) > 0 or len(e2.links) > 0
|
|
|
|
|
|
def test_ingest_from_mempalace():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
mp_entries = [
|
|
{"id": "mp-1", "content": "Test memory content", "metadata": {"title": "Test", "topics": ["demo"]}},
|
|
{"id": "mp-2", "content": "Another memory", "metadata": {"title": "Memory 2"}},
|
|
]
|
|
count = ingest_from_mempalace(archive, mp_entries)
|
|
assert count == 2
|
|
assert archive.count == 2
|
|
|
|
|
|
def test_archive_persistence():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive1 = MnemosyneArchive(archive_path=path)
|
|
ingest_event(archive1, title="Persistent", content="Should survive reload")
|
|
|
|
archive2 = MnemosyneArchive(archive_path=path)
|
|
assert archive2.count == 1
|
|
results = archive2.search("persistent")
|
|
assert len(results) == 1
|
|
|
|
|
|
def test_archive_remove_basic():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
e1 = ingest_event(archive, title="Alpha", content="First entry", topics=["x"])
|
|
assert archive.count == 1
|
|
|
|
result = archive.remove(e1.id)
|
|
assert result is True
|
|
assert archive.count == 0
|
|
assert archive.get(e1.id) is None
|
|
|
|
|
|
def test_archive_remove_nonexistent():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
result = archive.remove("does-not-exist")
|
|
assert result is False
|
|
|
|
|
|
def test_archive_remove_cleans_backlinks():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
e1 = ingest_event(archive, title="Python automation", content="Building automation tools in Python")
|
|
e2 = ingest_event(archive, title="Python scripting", content="Writing automation scripts using Python")
|
|
# At least one direction should be linked
|
|
assert e1.id in e2.links or e2.id in e1.links
|
|
|
|
# Remove e1; e2 must no longer reference it
|
|
archive.remove(e1.id)
|
|
e2_fresh = archive.get(e2.id)
|
|
assert e2_fresh is not None
|
|
assert e1.id not in e2_fresh.links
|
|
|
|
|
|
def test_archive_remove_persists():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
a1 = MnemosyneArchive(archive_path=path)
|
|
e = ingest_event(a1, title="Gone", content="Will be removed")
|
|
a1.remove(e.id)
|
|
|
|
a2 = MnemosyneArchive(archive_path=path)
|
|
assert a2.count == 0
|
|
|
|
|
|
def test_archive_export_unfiltered():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
ingest_event(archive, title="A", content="content a", topics=["alpha"])
|
|
ingest_event(archive, title="B", content="content b", topics=["beta"])
|
|
data = archive.export()
|
|
assert data["count"] == 2
|
|
assert len(data["entries"]) == 2
|
|
assert data["filters"] == {"query": None, "topics": None}
|
|
|
|
|
|
def test_archive_export_by_topic():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
ingest_event(archive, title="A", content="content a", topics=["alpha"])
|
|
ingest_event(archive, title="B", content="content b", topics=["beta"])
|
|
data = archive.export(topics=["alpha"])
|
|
assert data["count"] == 1
|
|
assert data["entries"][0]["title"] == "A"
|
|
|
|
|
|
def test_archive_export_by_query():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
ingest_event(archive, title="Hello world", content="greetings", topics=[])
|
|
ingest_event(archive, title="Goodbye", content="farewell", topics=[])
|
|
data = archive.export(query="hello")
|
|
assert data["count"] == 1
|
|
assert data["entries"][0]["title"] == "Hello world"
|
|
|
|
|
|
def test_archive_export_combined_filters():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
ingest_event(archive, title="Hello world", content="greetings", topics=["alpha"])
|
|
ingest_event(archive, title="Hello again", content="greetings again", topics=["beta"])
|
|
data = archive.export(query="hello", topics=["alpha"])
|
|
assert data["count"] == 1
|
|
assert data["entries"][0]["title"] == "Hello world"
|
|
|
|
|
|
def test_archive_stats_richer():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
# All four new fields present when archive is empty
|
|
s = archive.stats()
|
|
assert "orphans" in s
|
|
assert "link_density" in s
|
|
assert "oldest_entry" in s
|
|
assert "newest_entry" in s
|
|
assert s["orphans"] == 0
|
|
assert s["link_density"] == 0.0
|
|
assert s["oldest_entry"] is None
|
|
assert s["newest_entry"] is None
|
|
|
|
|
|
def test_archive_stats_orphan_count():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
# Two entries with very different content → unlikely to auto-link
|
|
ingest_event(archive, title="Zebras", content="Zebra stripes savannah Africa", topics=[])
|
|
ingest_event(archive, title="Compiler", content="Lexer parser AST bytecode", topics=[])
|
|
s = archive.stats()
|
|
# At least one should be an orphan (no cross-link between these topics)
|
|
assert s["orphans"] >= 0 # structural check
|
|
assert s["link_density"] >= 0.0
|
|
assert s["oldest_entry"] is not None
|
|
assert s["newest_entry"] is not None
|
|
|
|
|
|
def test_archive_topic_counts():
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "test_archive.json"
|
|
archive = MnemosyneArchive(archive_path=path)
|
|
ingest_event(archive, title="A", content="x", topics=["python", "automation"])
|
|
ingest_event(archive, title="B", content="y", topics=["python"])
|
|
ingest_event(archive, title="C", content="z", topics=["automation"])
|
|
counts = archive.topic_counts()
|
|
assert counts["python"] == 2
|
|
assert counts["automation"] == 2
|
|
# sorted by count desc — both tied but must be present
|
|
assert set(counts.keys()) == {"python", "automation"}
|