"""Tests for Mnemosyne CLI commands — path, touch, decay, vitality, fading, vibrant.""" import json import tempfile from pathlib import Path from unittest.mock import patch import sys import io import pytest from nexus.mnemosyne.archive import MnemosyneArchive from nexus.mnemosyne.entry import ArchiveEntry @pytest.fixture def archive(tmp_path): path = tmp_path / "test_archive.json" return MnemosyneArchive(archive_path=path) @pytest.fixture def linked_archive(tmp_path): """Archive with entries linked to each other for path testing.""" path = tmp_path / "test_archive.json" arch = MnemosyneArchive(archive_path=path, auto_embed=False) e1 = arch.add(ArchiveEntry(title="Alpha", content="first entry about python", topics=["code"])) e2 = arch.add(ArchiveEntry(title="Beta", content="second entry about python coding", topics=["code"])) e3 = arch.add(ArchiveEntry(title="Gamma", content="third entry about cooking recipes", topics=["food"])) return arch, e1, e2, e3 class TestPathCommand: def test_shortest_path_exists(self, linked_archive): arch, e1, e2, e3 = linked_archive path = arch.shortest_path(e1.id, e2.id) assert path is not None assert path[0] == e1.id assert path[-1] == e2.id def test_shortest_path_no_connection(self, linked_archive): arch, e1, e2, e3 = linked_archive # e3 (cooking) likely not linked to e1 (python coding) path = arch.shortest_path(e1.id, e3.id) # Path may or may not exist depending on linking threshold # Either None or a list is valid def test_shortest_path_same_entry(self, linked_archive): arch, e1, _, _ = linked_archive path = arch.shortest_path(e1.id, e1.id) assert path == [e1.id] def test_shortest_path_missing_entry(self, linked_archive): arch, e1, _, _ = linked_archive path = arch.shortest_path(e1.id, "nonexistent-id") assert path is None class TestTouchCommand: def test_touch_boosts_vitality(self, archive): entry = archive.add(ArchiveEntry(title="Test", content="Content")) # Simulate time passing by setting old last_accessed old_time = "2020-01-01T00:00:00+00:00" entry.last_accessed = old_time entry.vitality = 0.5 archive._save() touched = archive.touch(entry.id) assert touched.vitality > 0.5 assert touched.last_accessed != old_time def test_touch_missing_entry(self, archive): with pytest.raises(KeyError): archive.touch("nonexistent-id") class TestDecayCommand: def test_apply_decay_returns_stats(self, archive): archive.add(ArchiveEntry(title="Test", content="Content")) result = archive.apply_decay() assert result["total_entries"] == 1 assert "avg_vitality" in result assert "fading_count" in result assert "vibrant_count" in result def test_decay_on_empty_archive(self, archive): result = archive.apply_decay() assert result["total_entries"] == 0 assert result["avg_vitality"] == 0.0 class TestVitalityCommand: def test_get_vitality(self, archive): entry = archive.add(ArchiveEntry(title="Test", content="Content")) v = archive.get_vitality(entry.id) assert v["entry_id"] == entry.id assert v["title"] == "Test" assert 0.0 <= v["vitality"] <= 1.0 assert v["age_days"] >= 0 def test_get_vitality_missing(self, archive): with pytest.raises(KeyError): archive.get_vitality("nonexistent-id") class TestFadingVibrant: def test_fading_returns_sorted_ascending(self, archive): # Add entries with different vitalities e1 = archive.add(ArchiveEntry(title="Vibrant", content="High energy")) e2 = archive.add(ArchiveEntry(title="Fading", content="Low energy")) e2.vitality = 0.1 e2.last_accessed = "2020-01-01T00:00:00+00:00" archive._save() results = archive.fading(limit=10) assert len(results) == 2 assert results[0]["vitality"] <= results[1]["vitality"] def test_vibrant_returns_sorted_descending(self, archive): e1 = archive.add(ArchiveEntry(title="Fresh", content="New")) e2 = archive.add(ArchiveEntry(title="Old", content="Ancient")) e2.vitality = 0.1 e2.last_accessed = "2020-01-01T00:00:00+00:00" archive._save() results = archive.vibrant(limit=10) assert len(results) == 2 assert results[0]["vitality"] >= results[1]["vitality"] def test_fading_limit(self, archive): for i in range(15): archive.add(ArchiveEntry(title=f"Entry {i}", content=f"Content {i}")) results = archive.fading(limit=5) assert len(results) == 5 def test_vibrant_empty(self, archive): results = archive.vibrant() assert results == []