"""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 + "..."