diff --git a/nexus/mnemosyne/tests/test_path.py b/nexus/mnemosyne/tests/test_path.py new file mode 100644 index 00000000..895d3cf1 --- /dev/null +++ b/nexus/mnemosyne/tests/test_path.py @@ -0,0 +1,106 @@ +"""Tests for MnemosyneArchive.shortest_path and path_explanation.""" + +from nexus.mnemosyne.archive import MnemosyneArchive +from nexus.mnemosyne.entry import ArchiveEntry + + +def _make_archive(tmp_path): + archive = MnemosyneArchive(str(tmp_path / "test_archive.json")) + return archive + + +class TestShortestPath: + def test_direct_connection(self, tmp_path): + archive = _make_archive(tmp_path) + a = archive.add("Alpha", "first entry", topics=["start"]) + b = archive.add("Beta", "second entry", topics=["end"]) + # Manually link + a.links.append(b.id) + b.links.append(a.id) + archive._entries[a.id] = a + archive._entries[b.id] = b + archive._save() + + path = archive.shortest_path(a.id, b.id) + assert path == [a.id, b.id] + + def test_multi_hop_path(self, tmp_path): + archive = _make_archive(tmp_path) + a = archive.add("A", "alpha", topics=["x"]) + b = archive.add("B", "beta", topics=["y"]) + c = archive.add("C", "gamma", topics=["z"]) + # Chain: A -> B -> C + a.links.append(b.id) + b.links.extend([a.id, c.id]) + c.links.append(b.id) + archive._entries[a.id] = a + archive._entries[b.id] = b + archive._entries[c.id] = c + archive._save() + + path = archive.shortest_path(a.id, c.id) + assert path == [a.id, b.id, c.id] + + def test_no_path(self, tmp_path): + archive = _make_archive(tmp_path) + a = archive.add("A", "isolated", topics=[]) + b = archive.add("B", "also isolated", topics=[]) + path = archive.shortest_path(a.id, b.id) + assert path is None + + def test_same_entry(self, tmp_path): + archive = _make_archive(tmp_path) + a = archive.add("A", "lonely", topics=[]) + path = archive.shortest_path(a.id, a.id) + assert path == [a.id] + + def test_nonexistent_entry(self, tmp_path): + archive = _make_archive(tmp_path) + a = archive.add("A", "exists", topics=[]) + path = archive.shortest_path("fake-id", a.id) + assert path is None + + def test_shortest_of_multiple(self, tmp_path): + """When multiple paths exist, BFS returns shortest.""" + archive = _make_archive(tmp_path) + a = archive.add("A", "a", topics=[]) + b = archive.add("B", "b", topics=[]) + c = archive.add("C", "c", topics=[]) + d = archive.add("D", "d", topics=[]) + # A -> B -> D (short) + # A -> C -> B -> D (long) + a.links.extend([b.id, c.id]) + b.links.extend([a.id, d.id, c.id]) + c.links.extend([a.id, b.id]) + d.links.append(b.id) + for e in [a, b, c, d]: + archive._entries[e.id] = e + archive._save() + + path = archive.shortest_path(a.id, d.id) + assert len(path) == 3 # A -> B -> D, not A -> C -> B -> D + + +class TestPathExplanation: + def test_returns_step_details(self, tmp_path): + archive = _make_archive(tmp_path) + a = archive.add("Alpha", "the beginning", topics=["origin"]) + b = archive.add("Beta", "the middle", topics=["process"]) + a.links.append(b.id) + b.links.append(a.id) + archive._entries[a.id] = a + archive._entries[b.id] = b + archive._save() + + path = [a.id, b.id] + steps = archive.path_explanation(path) + assert len(steps) == 2 + assert steps[0]["title"] == "Alpha" + assert steps[1]["title"] == "Beta" + assert "origin" in steps[0]["topics"] + + def test_content_preview_truncation(self, tmp_path): + archive = _make_archive(tmp_path) + a = archive.add("A", "x" * 200, topics=[]) + steps = archive.path_explanation([a.id]) + assert len(steps[0]["content_preview"]) <= 123 # 120 + "..."