main
py 146 lines 4.76 KB
Raw
1 """Tests for scripts/wisdom_cap.py."""
2
3 import sys
4 from pathlib import Path
5
6 sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
7
8 from scripts.wisdom_cap import (
9 main,
10 parse_heuristics,
11 retire_heuristics,
12 select_for_retirement,
13 )
14
15 SAMPLE_WISDOM = """\
16 # Topic Wisdom
17
18 ## Signal Patterns
19 - Heuristic one about signals
20 - Heuristic two about patterns
21 - Heuristic three about growth
22
23 ## Noise Patterns
24 - Noise heuristic one
25 - Noise heuristic two
26
27 ## Scoring Adjustments
28 - Scoring rule one
29 - Scoring rule two
30 - Scoring rule three
31 """
32
33
34 class TestParseHeuristics:
35 def test_parses_all_bullets(self):
36 heuristics = parse_heuristics(SAMPLE_WISDOM)
37 assert len(heuristics) == 8
38
39 def test_captures_sections(self):
40 heuristics = parse_heuristics(SAMPLE_WISDOM)
41 sections = {h["section"] for h in heuristics}
42 assert "Signal Patterns" in sections
43 assert "Noise Patterns" in sections
44
45 def test_empty_content(self):
46 assert parse_heuristics("") == []
47 assert parse_heuristics("# Just a header\n") == []
48
49
50 class TestSelectForRetirement:
51 def test_selects_from_end(self):
52 heuristics = parse_heuristics(SAMPLE_WISDOM)
53 retired = select_for_retirement(heuristics, bytes_to_free=50)
54 # Should select from the end
55 assert retired[0]["text"] == "- Scoring rule three"
56
57 def test_respects_bytes_needed(self):
58 heuristics = parse_heuristics(SAMPLE_WISDOM)
59 retired = select_for_retirement(heuristics, bytes_to_free=1)
60 assert len(retired) >= 1
61
62
63 class TestRetireHeuristics:
64 def test_under_limit_no_action(self, tmp_path):
65 wisdom = tmp_path / "wisdom.md"
66 archive = tmp_path / "archive.md"
67 wisdom.write_text(SAMPLE_WISDOM)
68
69 result = retire_heuristics(wisdom, archive, limit=10000)
70 assert result["status"] == "ok"
71 assert not archive.exists()
72
73 def test_over_limit_retires(self, tmp_path):
74 wisdom = tmp_path / "wisdom.md"
75 archive = tmp_path / "archive.md"
76 wisdom.write_text(SAMPLE_WISDOM)
77 # Set limit below current size to trigger retirement
78 current_size = len(SAMPLE_WISDOM.encode("utf-8"))
79 limit = current_size - 50
80
81 result = retire_heuristics(wisdom, archive, limit=limit)
82 assert result["status"] == "retired"
83 assert result["retired_count"] >= 1
84 assert archive.exists()
85 # Wisdom should be smaller now
86 new_size = len(wisdom.read_text().encode("utf-8"))
87 assert new_size < current_size
88
89 def test_dry_run_no_changes(self, tmp_path):
90 wisdom = tmp_path / "wisdom.md"
91 archive = tmp_path / "archive.md"
92 wisdom.write_text(SAMPLE_WISDOM)
93 limit = 50 # Way under to force retirement
94
95 result = retire_heuristics(wisdom, archive, limit=limit, dry_run=True)
96 assert result["status"] == "dry_run"
97 assert result["would_retire"] > 0
98 # File should be unchanged
99 assert wisdom.read_text() == SAMPLE_WISDOM
100 assert not archive.exists()
101
102 def test_missing_wisdom(self, tmp_path):
103 wisdom = tmp_path / "nope.md"
104 archive = tmp_path / "archive.md"
105 result = retire_heuristics(wisdom, archive, limit=5120)
106 assert result["status"] == "skip"
107
108 def test_idempotent(self, tmp_path):
109 """Running twice produces same result if already under limit."""
110 wisdom = tmp_path / "wisdom.md"
111 archive = tmp_path / "archive.md"
112 wisdom.write_text(SAMPLE_WISDOM)
113 limit = len(SAMPLE_WISDOM.encode("utf-8")) - 50
114
115 retire_heuristics(wisdom, archive, limit=limit)
116 content_after_first = wisdom.read_text()
117
118 # Running again should be ok (under limit now)
119 result = retire_heuristics(wisdom, archive, limit=limit)
120 assert result["status"] == "ok"
121 assert wisdom.read_text() == content_after_first
122
123 def test_archive_appends(self, tmp_path):
124 """Multiple retirements append to archive, never overwrite."""
125 wisdom = tmp_path / "wisdom.md"
126 archive = tmp_path / "archive.md"
127 wisdom.write_text(SAMPLE_WISDOM)
128
129 # First retirement
130 retire_heuristics(wisdom, archive, limit=50)
131 first_archive = archive.read_text()
132
133 # Add more content and retire again
134 current = wisdom.read_text()
135 wisdom.write_text(current + "- New heuristic added\n" * 20)
136 retire_heuristics(wisdom, archive, limit=50)
137 second_archive = archive.read_text()
138
139 assert len(second_archive) > len(first_archive)
140
141
142 class TestMainCLI:
143 def test_runs_with_missing_topic(self, tmp_path, monkeypatch):
144 monkeypatch.setattr("scripts.wisdom_cap.SQUAD_DIR", tmp_path / ".squad" / "topics")
145 rc = main(["--topic", "nonexistent"])
146 assert rc == 0 # graceful skip