239 lines
5.7 KiB
Go
239 lines
5.7 KiB
Go
|
|
package cmd
|
||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TestAnalyzeCmd — FAST DEFAULT TEST (runs on make test / go test ./cmd)
|
||
|
|
func TestAnalyzeCmd(t *testing.T) {
|
||
|
|
t.Log("✓ Fast analyze unit test (no Grok API call)")
|
||
|
|
|
||
|
|
found := false
|
||
|
|
for _, c := range rootCmd.Commands() {
|
||
|
|
if c.Use == "analyze [flags]" {
|
||
|
|
found = true
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !found {
|
||
|
|
t.Fatal("analyze command not registered on rootCmd")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestAnalyzeFlagRegistration(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
flagName string
|
||
|
|
shorthand string
|
||
|
|
}{
|
||
|
|
{"output flag", "output", "o"},
|
||
|
|
{"yes flag", "yes", "y"},
|
||
|
|
{"dir flag", "dir", ""},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
flag := analyzeCmd.Flags().Lookup(tt.flagName)
|
||
|
|
if flag == nil {
|
||
|
|
t.Fatalf("flag %q not found on analyze command", tt.flagName)
|
||
|
|
}
|
||
|
|
if tt.shorthand != "" && flag.Shorthand != tt.shorthand {
|
||
|
|
t.Errorf("flag %q shorthand = %q, want %q", tt.flagName, flag.Shorthand, tt.shorthand)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDiscoverSourceFiles(t *testing.T) {
|
||
|
|
tmpDir := t.TempDir()
|
||
|
|
|
||
|
|
// Create some source files
|
||
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "main.go"), []byte("package main"), 0644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "helper.go"), []byte("package main"), 0644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
// Create a non-source file (should be ignored)
|
||
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "notes.txt"), []byte("notes"), 0644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
files, err := discoverSourceFiles(tmpDir)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("discoverSourceFiles failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(files) < 2 {
|
||
|
|
t.Errorf("expected at least 2 Go files, got %d: %v", len(files), files)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verify .txt was not included
|
||
|
|
for _, f := range files {
|
||
|
|
if strings.HasSuffix(f, ".txt") {
|
||
|
|
t.Errorf("unexpected .txt file in results: %s", f)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDiscoverSourceFilesSkipsDotDirs(t *testing.T) {
|
||
|
|
tmpDir := t.TempDir()
|
||
|
|
|
||
|
|
// Create a hidden directory with a Go file
|
||
|
|
hiddenDir := filepath.Join(tmpDir, ".hidden")
|
||
|
|
if err := os.MkdirAll(hiddenDir, 0755); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if err := os.WriteFile(filepath.Join(hiddenDir, "secret.go"), []byte("package hidden"), 0644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create vendor directory with a Go file
|
||
|
|
vendorDir := filepath.Join(tmpDir, "vendor")
|
||
|
|
if err := os.MkdirAll(vendorDir, 0755); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if err := os.WriteFile(filepath.Join(vendorDir, "dep.go"), []byte("package dep"), 0644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create a normal Go file
|
||
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "main.go"), []byte("package main"), 0644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
files, err := discoverSourceFiles(tmpDir)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("discoverSourceFiles failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, f := range files {
|
||
|
|
if strings.Contains(f, ".hidden") {
|
||
|
|
t.Errorf("should not include files from hidden dirs: %s", f)
|
||
|
|
}
|
||
|
|
if strings.Contains(f, "vendor") {
|
||
|
|
t.Errorf("should not include files from vendor dir: %s", f)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(files) != 1 {
|
||
|
|
t.Errorf("expected 1 file (main.go only), got %d: %v", len(files), files)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDiscoverSourceFilesSkipsBuildAndDist(t *testing.T) {
|
||
|
|
tmpDir := t.TempDir()
|
||
|
|
|
||
|
|
for _, dir := range []string{"build", "dist", "node_modules"} {
|
||
|
|
d := filepath.Join(tmpDir, dir)
|
||
|
|
if err := os.MkdirAll(d, 0755); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if err := os.WriteFile(filepath.Join(d, "file.go"), []byte("package x"), 0644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := os.WriteFile(filepath.Join(tmpDir, "main.go"), []byte("package main"), 0644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
files, err := discoverSourceFiles(tmpDir)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("discoverSourceFiles failed: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(files) != 1 {
|
||
|
|
t.Errorf("expected 1 file, got %d: %v", len(files), files)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestDiscoverSourceFilesEmptyDir(t *testing.T) {
|
||
|
|
tmpDir := t.TempDir()
|
||
|
|
|
||
|
|
files, err := discoverSourceFiles(tmpDir)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("discoverSourceFiles failed: %v", err)
|
||
|
|
}
|
||
|
|
if len(files) != 0 {
|
||
|
|
t.Errorf("expected 0 files for empty dir, got %d", len(files))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildProjectContext(t *testing.T) {
|
||
|
|
tmpDir := t.TempDir()
|
||
|
|
|
||
|
|
files := []string{
|
||
|
|
filepath.Join(tmpDir, "main.go"),
|
||
|
|
filepath.Join(tmpDir, "cmd", "root.go"),
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx := buildProjectContext(tmpDir, files)
|
||
|
|
|
||
|
|
if !strings.Contains(ctx, "Project Root:") {
|
||
|
|
t.Error("expected context to contain 'Project Root:'")
|
||
|
|
}
|
||
|
|
if !strings.Contains(ctx, "Total source files discovered: 2") {
|
||
|
|
t.Error("expected context to contain file count")
|
||
|
|
}
|
||
|
|
if !strings.Contains(ctx, "Key files") {
|
||
|
|
t.Error("expected context to contain 'Key files'")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPreviewLines(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
content string
|
||
|
|
n int
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "fewer lines than limit",
|
||
|
|
content: "line1\nline2\nline3\n",
|
||
|
|
n: 10,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "more lines than limit",
|
||
|
|
content: "a\nb\nc\nd\ne\nf\ng\nh\n",
|
||
|
|
n: 3,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "empty content",
|
||
|
|
content: "",
|
||
|
|
n: 5,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "zero lines requested",
|
||
|
|
content: "line1\nline2\n",
|
||
|
|
n: 0,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
// previewLines prints to stdout; just verify it doesn't panic
|
||
|
|
previewLines(tt.content, tt.n)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildProjectContextLimitsSize(t *testing.T) {
|
||
|
|
tmpDir := t.TempDir()
|
||
|
|
|
||
|
|
// Generate many files to test the 2500 byte limit
|
||
|
|
var files []string
|
||
|
|
for i := 0; i < 200; i++ {
|
||
|
|
files = append(files, filepath.Join(tmpDir, "file_with_a_long_name_for_testing_"+strings.Repeat("x", 20)+".go"))
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx := buildProjectContext(tmpDir, files)
|
||
|
|
|
||
|
|
// Should be reasonably bounded (the 2500 byte check truncates the file list)
|
||
|
|
if len(ctx) > 4000 {
|
||
|
|
t.Errorf("context unexpectedly large: %d bytes", len(ctx))
|
||
|
|
}
|
||
|
|
}
|