cartography_test.go raw
1 package cartography
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8 )
9
10 func TestSplitCamelCase(t *testing.T) {
11 tests := []struct {
12 in string
13 want []string
14 }{
15 {"ExtractSelfKnowledge", []string{"extract", "self", "knowledge"}},
16 {"Bond", []string{"bond"}},
17 {"A", nil}, // single char filtered out
18 {"goRoot", []string{"go", "root"}},
19 {"HTTPServer", []string{"h", "t", "t", "p", "server"}}, // not perfect but acceptable
20 }
21 for _, tt := range tests {
22 got := splitCamelCase(tt.in)
23 // Filter single chars for comparison.
24 var filtered []string
25 for _, w := range got {
26 if len(w) >= 2 {
27 filtered = append(filtered, w)
28 }
29 }
30 if len(filtered) == 0 {
31 filtered = nil
32 }
33 var wantFiltered []string
34 for _, w := range tt.want {
35 if len(w) >= 2 {
36 wantFiltered = append(wantFiltered, w)
37 }
38 }
39 if len(wantFiltered) == 0 {
40 wantFiltered = nil
41 }
42 if len(filtered) != len(wantFiltered) {
43 t.Errorf("splitCamelCase(%q) = %v, want %v", tt.in, filtered, wantFiltered)
44 }
45 }
46 }
47
48 func TestTypeBaseName(t *testing.T) {
49 tests := []struct {
50 in, want string
51 }{
52 {"*lattice.Lattice", "lattice"},
53 {"[]int", "int"},
54 {"string", "string"},
55 {"*Node", "node"},
56 {"context.Context", "context"},
57 }
58 for _, tt := range tests {
59 got := typeBaseName(tt.in)
60 if got != tt.want {
61 t.Errorf("typeBaseName(%q) = %q, want %q", tt.in, got, tt.want)
62 }
63 }
64 }
65
66 func TestExtractAll_LiveCodebase(t *testing.T) {
67 // Extract from the actual dendrite project.
68 root := findProjectRoot(t)
69 atlas, err := ExtractAll(root)
70 if err != nil {
71 t.Fatal(err)
72 }
73
74 if atlas.TotalEntries == 0 {
75 t.Fatal("expected entries from live codebase, got 0")
76 }
77
78 // Should have multiple packages.
79 pkgs := atlas.Packages()
80 if len(pkgs) < 5 {
81 t.Errorf("expected at least 5 packages, got %d: %v", len(pkgs), pkgs)
82 }
83
84 // Should have the lattice package.
85 latticeEntries := atlas.LookupPackage("lattice")
86 if len(latticeEntries) == 0 {
87 t.Error("expected entries in lattice package")
88 }
89
90 // Should find a known function.
91 e := atlas.LookupGo("lattice.New")
92 if e == nil {
93 // Try NewLattice or similar.
94 for _, entry := range latticeEntries {
95 if entry.Kind == "func" && strings.Contains(entry.Name, "New") {
96 e = entry
97 break
98 }
99 }
100 }
101 if e != nil {
102 if e.Description == "" {
103 t.Error("expected non-empty description for lattice entry")
104 }
105 if e.Confidence != 0.3 {
106 t.Errorf("expected mechanical confidence 0.3, got %.1f", e.Confidence)
107 }
108 }
109
110 t.Logf("atlas: %d entries across %d packages, mean confidence %.2f",
111 atlas.TotalEntries, len(pkgs), atlas.MeanConfidence)
112 }
113
114 func TestExtractTests_LiveCodebase(t *testing.T) {
115 root := findProjectRoot(t)
116 atlas, err := ExtractAll(root)
117 if err != nil {
118 t.Fatal(err)
119 }
120
121 before := countTested(atlas)
122
123 if err := ExtractTests(atlas, root); err != nil {
124 t.Fatal(err)
125 }
126
127 after := countTested(atlas)
128
129 if after <= before {
130 t.Error("expected test extraction to associate some tests")
131 }
132
133 // Check that confidence was bumped for tested entries.
134 for _, e := range atlas.Entries {
135 if e.HasTest && e.Source == "mechanical" && e.Confidence < 0.5 {
136 t.Errorf("%s has test but confidence %.1f < 0.5", e.ID, e.Confidence)
137 }
138 }
139
140 t.Logf("entries with tests: %d/%d", after, atlas.TotalEntries)
141 }
142
143 func TestLookupEnglish(t *testing.T) {
144 atlas := NewAtlas()
145 atlas.Add(&Entry{
146 ID: "lattice.Bond", Kind: "method", Package: "lattice",
147 Name: "Bond", Description: "Bonds an element to a lattice node.",
148 Concepts: []string{"lattice", "bond", "element"},
149 })
150 atlas.Add(&Entry{
151 ID: "grow.Run", Kind: "func", Package: "grow",
152 Name: "Run", Description: "Runs the growth engine with Brownian walkers.",
153 Concepts: []string{"grow", "run", "brownian", "walker"},
154 })
155 atlas.BuildIndexes()
156
157 // Search for "bond".
158 results := atlas.LookupEnglish("bond element")
159 if len(results) == 0 {
160 t.Fatal("expected results for 'bond element'")
161 }
162 if results[0].ID != "lattice.Bond" {
163 t.Errorf("expected lattice.Bond first, got %s", results[0].ID)
164 }
165
166 // Search for "brownian".
167 results = atlas.LookupEnglish("brownian walker")
168 if len(results) == 0 {
169 t.Fatal("expected results for 'brownian walker'")
170 }
171 if results[0].ID != "grow.Run" {
172 t.Errorf("expected grow.Run first, got %s", results[0].ID)
173 }
174 }
175
176 func TestMerge_PreservesOracle(t *testing.T) {
177 old := NewAtlas()
178 old.Add(&Entry{
179 ID: "lattice.Bond", Kind: "method", Package: "lattice",
180 Name: "Bond", Description: "Oracle says: bonds element with constraint check.",
181 Contract: "Takes *Node and Element, returns bool.",
182 Confidence: 0.7, Source: "oracle", Revision: 5,
183 })
184
185 fresh := NewAtlas()
186 fresh.Add(&Entry{
187 ID: "lattice.Bond", Kind: "method", Package: "lattice",
188 Name: "Bond", Description: "Method Bond on *Node takes (e Element) returns (bool).",
189 Signature: "func (*Node) Bond(e Element) bool",
190 Confidence: 0.3, Source: "mechanical",
191 })
192
193 old.Merge(fresh)
194
195 e := old.Entries["lattice.Bond"]
196 if e == nil {
197 t.Fatal("entry lost during merge")
198 }
199 // Oracle description should be preserved.
200 if !strings.Contains(e.Description, "Oracle says") {
201 t.Error("oracle description lost during merge")
202 }
203 // But mechanical signature should be updated.
204 if e.Signature != "func (*Node) Bond(e Element) bool" {
205 t.Errorf("signature not updated: %s", e.Signature)
206 }
207 if e.Confidence != 0.7 {
208 t.Errorf("confidence should stay 0.7, got %.1f", e.Confidence)
209 }
210 }
211
212 func TestSummary(t *testing.T) {
213 atlas := NewAtlas()
214 atlas.Add(&Entry{
215 ID: "lattice.New", Kind: "func", Package: "lattice",
216 Name: "New", Exported: true,
217 Signature: "func New() *Lattice",
218 Description: "Creates a new empty lattice structure.",
219 })
220 atlas.Add(&Entry{
221 ID: "lattice.Node", Kind: "type", Package: "lattice",
222 Name: "Node", Exported: true,
223 Signature: "type Node",
224 Description: "A position in the lattice with constraint envelope.",
225 })
226 atlas.BuildIndexes()
227
228 summary := atlas.Summary(0)
229 if !strings.Contains(summary, "Package lattice") {
230 t.Error("summary missing package header")
231 }
232 if !strings.Contains(summary, "func New()") {
233 t.Error("summary missing function signature")
234 }
235 }
236
237 func TestSaveAndLoad(t *testing.T) {
238 dir := t.TempDir()
239 path := filepath.Join(dir, "atlas.json")
240
241 atlas := NewAtlas()
242 atlas.Add(&Entry{
243 ID: "test.Foo", Kind: "func", Package: "test",
244 Name: "Foo", Description: "Does foo.",
245 Concepts: []string{"foo", "test"}, Confidence: 0.3,
246 })
247
248 if err := atlas.Save(path); err != nil {
249 t.Fatal(err)
250 }
251
252 loaded, err := Load(path)
253 if err != nil {
254 t.Fatal(err)
255 }
256
257 if loaded.TotalEntries != 1 {
258 t.Errorf("expected 1 entry, got %d", loaded.TotalEntries)
259 }
260
261 e := loaded.Entries["test.Foo"]
262 if e == nil {
263 t.Fatal("entry not found after load")
264 }
265 if e.Description != "Does foo." {
266 t.Errorf("description mismatch: %q", e.Description)
267 }
268
269 // Indexes should be rebuilt.
270 results := loaded.LookupEnglish("foo")
271 if len(results) == 0 {
272 t.Error("index not rebuilt after load")
273 }
274 }
275
276 func TestParseEnrichResponse(t *testing.T) {
277 entry := &Entry{ID: "test.Foo"}
278 answer := `DESCRIPTION: This function computes the hash of its input.
279 It uses SHA-256 for consistency.
280
281 CONTRACT: Takes a byte slice, returns a 32-byte hash and nil error.
282 Returns error if input is nil.
283
284 EDGE CASES: Nil input returns ErrNilInput. Empty input returns hash of empty string.
285
286 VALIDATION: Verify hash of "hello" matches known SHA-256 digest.`
287
288 parseEnrichResponse(entry, answer)
289
290 if !strings.Contains(entry.Description, "SHA-256") {
291 t.Errorf("description not parsed: %q", entry.Description)
292 }
293 if !strings.Contains(entry.Contract, "32-byte hash") {
294 t.Errorf("contract not parsed: %q", entry.Contract)
295 }
296 if !strings.Contains(entry.EdgeCases, "ErrNilInput") {
297 t.Errorf("edge cases not parsed: %q", entry.EdgeCases)
298 }
299 if !strings.Contains(entry.Validation, "SHA-256 digest") {
300 t.Errorf("validation not parsed: %q", entry.Validation)
301 }
302 }
303
304 // findProjectRoot walks up from the test file to find the go.mod.
305 func findProjectRoot(t *testing.T) string {
306 t.Helper()
307 dir, err := os.Getwd()
308 if err != nil {
309 t.Fatal(err)
310 }
311 for {
312 if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
313 return dir
314 }
315 parent := filepath.Dir(dir)
316 if parent == dir {
317 t.Fatal("could not find project root (no go.mod)")
318 }
319 dir = parent
320 }
321 }
322
323 func countTested(atlas *Atlas) int {
324 n := 0
325 for _, e := range atlas.Entries {
326 if e.HasTest {
327 n++
328 }
329 }
330 return n
331 }
332