Skip to main content
Glama
indexer_test.go12.3 kB
package proto import ( "log/slog" "os" "path/filepath" "testing" ) func TestIndexDirectory(t *testing.T) { // Create test index logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelError, // Quiet during tests })) index := NewProtoIndex(logger) // Index the example directory (if it exists) exampleDir := "../../../python-version/examples" if _, err := os.Stat(exampleDir); os.IsNotExist(err) { t.Skip("Example directory not found, skipping test") } count, err := index.IndexDirectory(exampleDir) if err != nil { t.Fatalf("Failed to index directory: %v", err) } if count == 0 { t.Error("Expected to index at least one proto file") } stats := index.GetStats() if stats.TotalFiles != count { t.Errorf("Expected %d files in stats, got %d", count, stats.TotalFiles) } t.Logf("Indexed %d files: %d services, %d messages, %d enums", stats.TotalFiles, stats.TotalServices, stats.TotalMessages, stats.TotalEnums) } func TestSearchInNames(t *testing.T) { index := createTestIndex(t) tests := []struct { name string query string minScore int wantMin int // Minimum expected results wantFirst string // Expected first result name (if any) }{ { name: "exact service match", query: "UserService", minScore: 60, wantMin: 0, // Fuzzy search may not find exact full path matches }, { name: "partial service match", query: "User", minScore: 60, wantMin: 1, // Should find services and messages with "User" }, { name: "fuzzy match", query: "UsrSvc", // fuzzy for UserService minScore: 60, wantMin: 0, // Might not match depending on fuzzy algorithm }, { name: "case insensitive", query: "user", minScore: 60, wantMin: 1, // Should find items with "user" in name or comment }, { name: "comment search", query: "operations", minScore: 60, wantMin: 0, // May find in comments }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { results := index.Search(tt.query, 20, tt.minScore) if len(results) < tt.wantMin { t.Errorf("Search(%q) returned %d results, want at least %d", tt.query, len(results), tt.wantMin) } else { t.Logf("Search(%q) returned %d results", tt.query, len(results)) } if tt.wantFirst != "" && len(results) > 0 { if results[0].Name != tt.wantFirst { t.Logf("Search(%q) first result = %q, want %q (may vary based on scoring)", tt.query, results[0].Name, tt.wantFirst) } } // Verify results are sorted by score (descending) for i := 1; i < len(results); i++ { if results[i].Score > results[i-1].Score { t.Errorf("Results not sorted by score: results[%d].Score=%d > results[%d].Score=%d", i, results[i].Score, i-1, results[i-1].Score) } } // Log some results for inspection for i, result := range results { if i < 3 { // Log first 3 t.Logf(" [%d] %s (score=%d, type=%s, match=%s)", i, result.Name, result.Score, result.Type, result.MatchType) } } }) } } func TestSearchInFields(t *testing.T) { index := createTestIndex(t) // Search for a field name that exists in User message results := index.Search("email", 20, 60) // Should find messages containing "email" field found := false for _, result := range results { if result.Type == "message" && result.MatchType == "field" { found = true if result.MatchedField == "" { t.Error("Expected MatchedField to be set for field match") } t.Logf("Found field match: %s in %s (field: %s, score: %d)", result.MatchType, result.Name, result.MatchedField, result.Score) break } } if !found { t.Log("Field search may not have found results (depends on test data)") } } func TestSearchInRPCs(t *testing.T) { index := createTestIndex(t) // Search for an RPC name results := index.Search("GetUser", 20, 60) // Should find services containing this RPC foundService := false for _, result := range results { if result.Type == "service" { foundService = true if result.RPCCount == 0 { t.Error("Expected service to have RPCs") } t.Logf("Found service: %s with %d RPCs (score: %d)", result.Name, result.RPCCount, result.Score) break } } if !foundService { t.Log("Service search may not have found results (depends on test data)") } } func TestSearchInComments(t *testing.T) { index := createTestIndex(t) // Search for text that appears in comments results := index.Search("user", 20, 60) // Should find definitions with "user" in comments for _, result := range results { if result.MatchType == "comment" && result.Comment == "" { t.Error("Expected comment to be set for comment match") } t.Logf("Found %s match: %s (type: %s, score: %d)", result.MatchType, result.Name, result.Type, result.Score) } if len(results) == 0 { t.Log("Comment search may not have found results (depends on test data)") } } func TestSearchLimit(t *testing.T) { index := createTestIndex(t) limits := []int{1, 5, 10, 20} for _, limit := range limits { t.Run(string(rune(limit)), func(t *testing.T) { results := index.Search("user", limit, 50) if len(results) > limit { t.Errorf("Search returned %d results, limit was %d", len(results), limit) } }) } } func TestSearchMinScore(t *testing.T) { index := createTestIndex(t) // Test with different min scores minScores := []int{50, 70, 90} for _, minScore := range minScores { t.Run(string(rune(minScore)), func(t *testing.T) { results := index.Search("user", 20, minScore) // All results should have score >= minScore for _, result := range results { if result.Score < minScore { t.Errorf("Result score %d is less than minScore %d", result.Score, minScore) } } }) } } func TestSearchEmptyQuery(t *testing.T) { index := createTestIndex(t) results := index.Search("", 20, 60) if results != nil { t.Error("Expected nil results for empty query") } } func TestSearchNoResults(t *testing.T) { index := createTestIndex(t) // Search for something that definitely doesn't exist results := index.Search("xyznonexistent12345", 20, 60) if len(results) != 0 { t.Errorf("Expected no results for non-existent query, got %d", len(results)) } } func TestGetService(t *testing.T) { index := createTestIndex(t) // Try to get a service service, err := index.GetService("UserService", false, 0) if err != nil { t.Logf("GetService returned error (may be expected if test data not available): %v", err) return } if service == nil { t.Fatal("Expected service to be non-nil") } // Check required fields if name, ok := service["name"].(string); !ok || name == "" { t.Error("Expected service to have name") } if fullName, ok := service["full_name"].(string); !ok || fullName == "" { t.Error("Expected service to have full_name") } if rpcs, ok := service["rpcs"].([]map[string]interface{}); !ok || len(rpcs) == 0 { t.Error("Expected service to have rpcs") } t.Logf("Got service: %v", service["full_name"]) } func TestGetMessage(t *testing.T) { index := createTestIndex(t) // Try to get a message message, err := index.GetMessage("User", false, 0) if err != nil { t.Logf("GetMessage returned error (may be expected if test data not available): %v", err) return } if message == nil { t.Fatal("Expected message to be non-nil") } // Check required fields if name, ok := message["name"].(string); !ok || name == "" { t.Error("Expected message to have name") } if fullName, ok := message["full_name"].(string); !ok || fullName == "" { t.Error("Expected message to have full_name") } if fields, ok := message["fields"].([]map[string]interface{}); !ok { t.Error("Expected message to have fields") } else { t.Logf("Message has %d fields", len(fields)) } t.Logf("Got message: %v", message["full_name"]) } func TestGetEnum(t *testing.T) { index := createTestIndex(t) // Try to get an enum enum, err := index.GetEnum("UserRole") if err != nil { t.Logf("GetEnum returned error (may be expected if test data not available): %v", err) return } if enum == nil { t.Fatal("Expected enum to be non-nil") } // Check required fields if name, ok := enum["name"].(string); !ok || name == "" { t.Error("Expected enum to have name") } if values, ok := enum["values"].([]map[string]interface{}); !ok { t.Error("Expected enum to have values") } else { t.Logf("Enum has %d values", len(values)) } t.Logf("Got enum: %v", enum["full_name"]) } func TestConcurrentSearch(t *testing.T) { index := createTestIndex(t) // Test concurrent searches (index should be thread-safe with RWMutex) done := make(chan bool) for i := 0; i < 10; i++ { go func() { results := index.Search("user", 20, 60) _ = results done <- true }() } // Wait for all goroutines for i := 0; i < 10; i++ { <-done } t.Log("Concurrent search test passed") } func TestIndexFile(t *testing.T) { // Create a temporary test proto file tmpDir := t.TempDir() testFile := filepath.Join(tmpDir, "test.proto") testContent := `syntax = "proto3"; package test; // Test service for indexing service TestService { // Get test data rpc GetTest(TestRequest) returns (TestResponse); } message TestRequest { string id = 1; // Test ID } message TestResponse { string data = 1; // Test data } ` if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelError, })) index := NewProtoIndex(logger) // Index the file if err := index.IndexFile(testFile); err != nil { t.Fatalf("Failed to index file: %v", err) } // Verify it was indexed stats := index.GetStats() if stats.TotalFiles != 1 { t.Errorf("Expected 1 file, got %d", stats.TotalFiles) } if stats.TotalServices != 1 { t.Errorf("Expected 1 service, got %d", stats.TotalServices) } if stats.TotalMessages != 2 { t.Errorf("Expected 2 messages, got %d", stats.TotalMessages) } // Search for the service (case-insensitive and should match comments) results := index.Search("test", 10, 50) if len(results) == 0 { t.Error("Expected to find test-related definitions") } else { t.Logf("Found %d results for 'test' query", len(results)) for _, result := range results { t.Logf(" - %s (score=%d, match=%s)", result.Name, result.Score, result.MatchType) } } } func TestRemoveFile(t *testing.T) { // Create a temporary test proto file tmpDir := t.TempDir() testFile := filepath.Join(tmpDir, "test.proto") testContent := `syntax = "proto3"; package test; service TestService { rpc GetTest(TestRequest) returns (TestResponse); } message TestRequest { string id = 1; } message TestResponse { string data = 1; } ` if err := os.WriteFile(testFile, []byte(testContent), 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelError, })) index := NewProtoIndex(logger) // Index the file if err := index.IndexFile(testFile); err != nil { t.Fatalf("Failed to index file: %v", err) } // Verify it was indexed stats := index.GetStats() if stats.TotalFiles != 1 { t.Error("Expected file to be indexed") } // Remove the file index.RemoveFile(testFile) // Verify it was removed stats = index.GetStats() if stats.TotalFiles != 0 { t.Errorf("Expected 0 files after removal, got %d", stats.TotalFiles) } if stats.TotalServices != 0 { t.Errorf("Expected 0 services after removal, got %d", stats.TotalServices) } } // Helper function to create a test index with example data func createTestIndex(t *testing.T) *ProtoIndex { logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelError, // Quiet during tests })) index := NewProtoIndex(logger) // Try to index the example directory exampleDir := "../../../python-version/examples" if _, err := os.Stat(exampleDir); os.IsNotExist(err) { t.Skip("Example directory not found, skipping test") } _, err := index.IndexDirectory(exampleDir) if err != nil { t.Fatalf("Failed to create test index: %v", err) } return index }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/umuterturk/mcp-proto'

If you have feedback or need assistance with the MCP directory API, please join our Discord server