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