package analysis import "testing" func TestPureClusterLabel(t *testing.T) { tests := []struct { name string files []string want string }{ { name: "all files under one deep dir", files: []string{ "gortex/internal/parser/languages/cpp.go", "gortex/internal/parser/languages/dart.go", "gortex/internal/parser/languages/python.go", }, want: "parser/languages", }, { name: "all files under repo + plumbing only", files: []string{ "gortex/internal/parser/extractor.go", "gortex/internal/graph/edge.go", }, want: "", }, { name: "single file → plumbing stripped from directory", files: []string{ "gortex/internal/server/dashboard.go", }, want: "server", }, { name: "empty", files: []string{}, want: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := pureClusterLabel(tt.files) if got != tt.want { t.Errorf("pureClusterLabel(%v) = %q; want %q", tt.files, got, tt.want) } }) } } func TestMixedClusterLabel(t *testing.T) { tests := []struct { name string files []string want string }{ { name: "spread across many top-level dirs surfaces spread", files: []string{ "gortex/internal/parser/languages/abap.go", "gortex/internal/parser/languages/actionscript.go", "gortex/internal/parser/languages/ada.go", "gortex/internal/parser/extractor.go", "gortex/internal/dataflow/dataflow.go", "gortex/internal/graph/edge.go", "gortex/internal/graph/node.go", "gortex/internal/mcp/tools_ast.go", "gortex/internal/llm/daemon_backend.go", }, want: "parser/languages +5 dirs", }, { name: "two dirs, modal wins with +1 dirs", files: []string{ "gortex/internal/foo/a.go", "gortex/internal/foo/b.go", "gortex/internal/bar/x.go", }, want: "foo +1 dirs", }, { name: "single dir → no spread annotation", files: []string{ "gortex/internal/foo/a.go", "gortex/internal/foo/b.go", }, want: "foo", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := mixedClusterLabel(tt.files) if got != tt.want { t.Errorf("mixedClusterLabel = %q; want %q", got, tt.want) } }) } } func TestDisambiguateLabelsCascadesToSizeSuffix(t *testing.T) { // Two clusters share modal dir AND first-file AND second-file — // only the size suffix breaks the tie. communities := []Community{ {ID: "a", Label: "server", Size: 50, Files: []string{"server/h.go", "server/m.go"}}, {ID: "b", Label: "server", Size: 70, Files: []string{"server/h.go", "server/m.go"}}, } disambiguateLabels(communities) if communities[0].Label == communities[1].Label { t.Fatalf("size tiebreaker failed: both %q", communities[0].Label) } } func TestDisambiguateLabelsOrdinalTiebreaker(t *testing.T) { // Three clusters with the same dir, same first two files, AND // the same size — only the ordinal tiebreaker can split them. communities := []Community{ {ID: "a", Label: "mcp", Size: 15, Files: []string{"mcp/memories.go", "mcp/notes.go"}}, {ID: "b", Label: "mcp", Size: 15, Files: []string{"mcp/memories.go", "mcp/notes.go"}}, {ID: "c", Label: "mcp", Size: 15, Files: []string{"mcp/memories.go", "mcp/notes.go"}}, } disambiguateLabels(communities) labels := map[string]bool{} for _, c := range communities { if labels[c.Label] { t.Fatalf("ordinal tiebreaker failed: duplicate %q", c.Label) } labels[c.Label] = true } } func TestDisambiguateLabelsUsesHubFirst(t *testing.T) { // Two pure clusters under the same directory should get their // hub-symbol names appended — the most meaningful disambiguator. // File basenames are only the fallback when the hub is missing. communities := []Community{ { ID: "community-1", Label: "parser/languages", Hub: "GoExtractor", Files: []string{ "gortex/internal/parser/languages/cpp.go", "gortex/internal/parser/languages/csharp.go", }, }, { ID: "community-2", Label: "parser/languages", Hub: "DartExtractor", Files: []string{ "gortex/internal/parser/languages/dart.go", "gortex/internal/parser/languages/flutter.go", }, }, { ID: "community-3", Label: "server", Hub: "RunServer", Files: []string{"gortex/internal/server/handler.go"}, }, } disambiguateLabels(communities) wantA := "parser/languages · GoExtractor" wantB := "parser/languages · DartExtractor" if communities[0].Label != wantA { t.Errorf("community[0] = %q; want %q", communities[0].Label, wantA) } if communities[1].Label != wantB { t.Errorf("community[1] = %q; want %q", communities[1].Label, wantB) } if communities[2].Label != "server" { t.Errorf("unique label was modified: %q", communities[2].Label) } } func TestDisambiguateFallsBackToFileWhenNoHub(t *testing.T) { // When the hub is missing (e.g. legacy data, no member with a // resolvable name), the cascade falls back to file basenames. communities := []Community{ { ID: "a", Label: "parser/languages", Hub: "", Files: []string{"gortex/internal/parser/languages/cpp.go"}, }, { ID: "b", Label: "parser/languages", Hub: "", Files: []string{"gortex/internal/parser/languages/dart.go"}, }, } disambiguateLabels(communities) if communities[0].Label == communities[1].Label { t.Fatalf("fallback failed: both %q", communities[0].Label) } } func TestCleanHubName(t *testing.T) { cases := map[string]string{ "": "", "runServer": "runServer", "(*Extractor).Extract": "Extractor.Extract", "(Receiver).Method": "Receiver.Method", "WayTooLongFunctionNameOfAReallyLongType.WithAMethod": "WayTooLongFunctionNameOfAReally…", } for in, want := range cases { got := cleanHubName(in) if got != want { t.Errorf("cleanHubName(%q) = %q; want %q", in, got, want) } } } func TestInferCommunityLabelDistinguishesSameBasename(t *testing.T) { // Two clusters whose basenames collide ("languages") but whose // contents differ should produce different labels. pureFiles := []string{ "gortex/internal/parser/languages/cpp.go", "gortex/internal/parser/languages/dart.go", "gortex/internal/parser/languages/python.go", } mixedFiles := []string{ "gortex/internal/parser/languages/abap.go", "gortex/internal/parser/extractor.go", "gortex/internal/dataflow/dataflow.go", "gortex/internal/graph/edge.go", "gortex/internal/mcp/tools_ast.go", } pure := inferCommunityLabel(nil, nil, pureFiles) mixed := inferCommunityLabel(nil, nil, mixedFiles) if pure == mixed { t.Fatalf("two structurally distinct clusters share label %q (regression — naming collision is back)", pure) } if pure != "parser/languages" { t.Errorf("pure cluster label = %q; want %q", pure, "parser/languages") } if mixed == "languages" || mixed == "parser/languages" { t.Errorf("mixed cluster label = %q; should surface its spread", mixed) } }