| package tools |
| |
| import ( |
| "context" |
| "strings" |
| "testing" |
| |
| "github.com/korap/korap-mcp/service" |
| "github.com/mark3labs/mcp-go/mcp" |
| "github.com/stretchr/testify/assert" |
| ) |
| |
| func TestMetadataTool_Name(t *testing.T) { |
| client := &service.Client{} |
| tool := NewMetadataTool(client) |
| |
| assert.Equal(t, "korap_metadata", tool.Name()) |
| } |
| |
| func TestMetadataTool_Description(t *testing.T) { |
| client := &service.Client{} |
| tool := NewMetadataTool(client) |
| |
| expected := "Retrieve metadata and statistics for KorAP corpora" |
| assert.Equal(t, expected, tool.Description()) |
| } |
| |
| func TestMetadataTool_InputSchema(t *testing.T) { |
| client := &service.Client{} |
| tool := NewMetadataTool(client) |
| |
| schema := tool.InputSchema() |
| |
| // Verify it's an object type |
| assert.Equal(t, "object", schema["type"]) |
| assert.Equal(t, "KorAP Metadata Parameters", schema["title"]) |
| assert.Contains(t, schema["description"], "Parameters for retrieving corpus metadata") |
| assert.Equal(t, false, schema["additionalProperties"]) |
| |
| // Verify properties exist |
| properties, ok := schema["properties"].(map[string]interface{}) |
| assert.True(t, ok) |
| assert.Contains(t, properties, "action") |
| assert.Contains(t, properties, "corpus") |
| |
| // Verify action property details |
| action, ok := properties["action"].(map[string]interface{}) |
| assert.True(t, ok) |
| assert.Equal(t, "string", action["type"]) |
| assert.Contains(t, action["description"], "Type of metadata operation") |
| |
| enum, ok := action["enum"].([]string) |
| assert.True(t, ok) |
| assert.Contains(t, enum, "list") |
| assert.Contains(t, enum, "statistics") |
| assert.Equal(t, "list", action["default"]) |
| |
| actionExamples, ok := action["examples"].([]string) |
| assert.True(t, ok) |
| assert.Contains(t, actionExamples, "list") |
| assert.Contains(t, actionExamples, "statistics") |
| |
| // Verify corpus property details |
| corpus, ok := properties["corpus"].(map[string]interface{}) |
| assert.True(t, ok) |
| assert.Equal(t, "string", corpus["type"]) |
| assert.Contains(t, corpus["description"], "Virtual corpus query") |
| assert.Contains(t, corpus["examples"], "corpusSigle = \"GOE\"") |
| |
| // Verify required fields |
| required, ok := schema["required"].([]string) |
| assert.True(t, ok) |
| assert.Contains(t, required, "action") |
| assert.Len(t, required, 1) // Only action should be required |
| } |
| |
| func TestNewMetadataTool(t *testing.T) { |
| client := &service.Client{} |
| tool := NewMetadataTool(client) |
| |
| assert.NotNil(t, tool) |
| assert.Equal(t, client, tool.client) |
| } |
| |
| func TestMetadataTool_Execute_MissingAction(t *testing.T) { |
| client := &service.Client{} |
| tool := NewMetadataTool(client) |
| |
| // Create request without action parameter |
| request := mcp.CallToolRequest{ |
| Params: mcp.CallToolParams{ |
| Arguments: map[string]interface{}{}, |
| }, |
| } |
| |
| _, err := tool.Execute(context.Background(), request) |
| assert.Error(t, err) |
| assert.Contains(t, err.Error(), "action parameter is required") |
| } |
| |
| func TestMetadataTool_Execute_NilClient(t *testing.T) { |
| tool := NewMetadataTool(nil) |
| |
| request := mcp.CallToolRequest{ |
| Params: mcp.CallToolParams{ |
| Arguments: map[string]interface{}{ |
| "action": "list", |
| }, |
| }, |
| } |
| |
| _, err := tool.Execute(context.Background(), request) |
| assert.Error(t, err) |
| assert.Contains(t, err.Error(), "KorAP client not configured") |
| } |
| |
| func TestMetadataTool_Execute_UnknownAction(t *testing.T) { |
| client := &service.Client{} |
| tool := NewMetadataTool(client) |
| |
| request := mcp.CallToolRequest{ |
| Params: mcp.CallToolParams{ |
| Arguments: map[string]interface{}{ |
| "action": "unknown", |
| }, |
| }, |
| } |
| |
| _, err := tool.Execute(context.Background(), request) |
| assert.Error(t, err) |
| // The validation error should come before authentication |
| assert.Contains(t, err.Error(), "invalid metadata request") |
| assert.Contains(t, err.Error(), "invalid action") |
| } |
| |
| func TestMetadataTool_Execute_StatisticsWithoutCorpus(t *testing.T) { |
| tool := NewMetadataTool(nil) |
| |
| request := mcp.CallToolRequest{ |
| Params: mcp.CallToolParams{ |
| Arguments: map[string]interface{}{ |
| "action": "statistics", |
| }, |
| }, |
| } |
| |
| _, err := tool.Execute(context.Background(), request) |
| assert.Error(t, err) |
| // Should fail at client check since corpus is now optional |
| assert.Contains(t, err.Error(), "KorAP client not configured") |
| } |
| |
| func TestMetadataTool_Execute_ParameterExtraction(t *testing.T) { |
| // This test verifies that parameters are extracted correctly |
| // It should fail with authentication error since we don't have a mock server |
| // but we can verify the parameters were parsed correctly by checking the log messages |
| |
| client := &service.Client{} |
| tool := NewMetadataTool(client) |
| |
| tests := []struct { |
| name string |
| arguments map[string]interface{} |
| expectErr bool |
| }{ |
| { |
| name: "list_action", |
| arguments: map[string]interface{}{ |
| "action": "list", |
| }, |
| expectErr: true, // Will fail at authentication |
| }, |
| { |
| name: "statistics_action", |
| arguments: map[string]interface{}{ |
| "action": "statistics", |
| "corpus": "test-corpus", |
| }, |
| expectErr: true, // Will fail at authentication |
| }, |
| { |
| name: "statistics_with_empty_corpus", |
| arguments: map[string]interface{}{ |
| "action": "statistics", |
| "corpus": "", |
| }, |
| expectErr: true, // Will fail at authentication (corpus is optional) |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| request := mcp.CallToolRequest{ |
| Params: mcp.CallToolParams{ |
| Arguments: tt.arguments, |
| }, |
| } |
| |
| _, err := tool.Execute(context.Background(), request) |
| if tt.expectErr { |
| assert.Error(t, err) |
| } |
| }) |
| } |
| } |
| |
| func TestMetadataTool_formatCorpusList(t *testing.T) { |
| client := &service.Client{} |
| tool := NewMetadataTool(client) |
| |
| // Test empty response |
| emptyResponse := &service.CorpusListResponse{ |
| Corpora: []service.CorpusInfo{}, |
| } |
| |
| result := tool.formatCorpusList(emptyResponse) |
| assert.Contains(t, result, "KorAP Available Corpora") |
| assert.Contains(t, result, "No corpora available") |
| |
| // Test response with corpora |
| responseWithCorpora := &service.CorpusListResponse{ |
| Corpora: []service.CorpusInfo{ |
| { |
| ID: "corpus1", |
| Name: "Test Corpus 1", |
| Description: "A test corpus", |
| Documents: 100, |
| Tokens: 50000, |
| Sentences: 2500, |
| Paragraphs: 500, |
| }, |
| { |
| ID: "corpus2", |
| Name: "Test Corpus 2", |
| Documents: 200, |
| Tokens: 75000, |
| }, |
| }, |
| } |
| |
| result = tool.formatCorpusList(responseWithCorpora) |
| assert.Contains(t, result, "KorAP Available Corpora") |
| assert.Contains(t, result, "Total Corpora: 2") |
| assert.Contains(t, result, "1. Test Corpus 1") |
| assert.Contains(t, result, "ID: corpus1") |
| assert.Contains(t, result, "Description: A test corpus") |
| assert.Contains(t, result, "Documents: 100") |
| assert.Contains(t, result, "Tokens: 50000") |
| assert.Contains(t, result, "Sentences: 2500") |
| assert.Contains(t, result, "Paragraphs: 500") |
| assert.Contains(t, result, "2. Test Corpus 2") |
| assert.Contains(t, result, "ID: corpus2") |
| assert.Contains(t, result, "Documents: 200") |
| assert.Contains(t, result, "Tokens: 75000") |
| } |
| |
| func TestMetadataTool_formatCorpusStatistics(t *testing.T) { |
| client := &service.Client{} |
| tool := NewMetadataTool(client) |
| |
| // Test minimal statistics |
| minimalStats := &service.StatisticsResponse{ |
| Documents: 100, |
| Tokens: 50000, |
| } |
| |
| result := tool.formatCorpusStatistics("test-corpus", minimalStats) |
| assert.Contains(t, result, "KorAP Corpus Statistics") |
| assert.Contains(t, result, "Corpus Query: test-corpus") |
| assert.Contains(t, result, "Documents: 100") |
| assert.Contains(t, result, "Tokens: 50000") |
| |
| // Test complete statistics with additional fields |
| completeStats := &service.StatisticsResponse{ |
| Documents: 200, |
| Tokens: 100000, |
| Sentences: 5000, |
| Paragraphs: 1000, |
| Fields: map[string]interface{}{ |
| "genre": "literature", |
| "language": "German", |
| "year": 2023, |
| }, |
| } |
| |
| result = tool.formatCorpusStatistics("complete-corpus", completeStats) |
| assert.Contains(t, result, "KorAP Corpus Statistics") |
| assert.Contains(t, result, "Corpus Query: complete-corpus") |
| assert.Contains(t, result, "Documents: 200") |
| assert.Contains(t, result, "Tokens: 100000") |
| assert.Contains(t, result, "Sentences: 5000") |
| assert.Contains(t, result, "Paragraphs: 1000") |
| assert.Contains(t, result, "Additional Fields:") |
| assert.Contains(t, result, "genre: literature") |
| assert.Contains(t, result, "language: German") |
| assert.Contains(t, result, "year: 2023") |
| |
| // Test empty corpus query (all available data) |
| result = tool.formatCorpusStatistics("", minimalStats) |
| assert.Contains(t, result, "KorAP Corpus Statistics") |
| assert.Contains(t, result, "Corpus Query: (all available data)") |
| assert.Contains(t, result, "Documents: 100") |
| assert.Contains(t, result, "Tokens: 50000") |
| } |
| |
| func TestMetadataTool_SchemaCompliance(t *testing.T) { |
| // Test various parameter combinations against the schema |
| client := &service.Client{} |
| tool := NewMetadataTool(client) |
| |
| tests := []struct { |
| name string |
| arguments map[string]interface{} |
| expectValid bool |
| errorMsg string |
| }{ |
| { |
| name: "valid_list_minimal", |
| arguments: map[string]interface{}{ |
| "action": "list", |
| }, |
| expectValid: true, |
| }, |
| { |
| name: "valid_statistics_minimal", |
| arguments: map[string]interface{}{ |
| "action": "statistics", |
| }, |
| expectValid: true, |
| }, |
| { |
| name: "valid_statistics_with_corpus", |
| arguments: map[string]interface{}{ |
| "action": "statistics", |
| "corpus": "test-corpus", |
| }, |
| expectValid: true, |
| }, |
| { |
| name: "valid_list_with_corpus_ignored", |
| arguments: map[string]interface{}{ |
| "action": "list", |
| "corpus": "test-corpus", // Should be ignored for list action |
| }, |
| expectValid: true, |
| }, |
| { |
| name: "missing_required_action", |
| arguments: map[string]interface{}{ |
| "corpus": "test-corpus", |
| }, |
| expectValid: false, |
| errorMsg: "action parameter is required", |
| }, |
| { |
| name: "invalid_action", |
| arguments: map[string]interface{}{ |
| "action": "invalid", |
| }, |
| expectValid: false, |
| errorMsg: "invalid action", |
| }, |
| { |
| name: "empty_action", |
| arguments: map[string]interface{}{ |
| "action": "", |
| }, |
| expectValid: false, |
| errorMsg: "action is required and cannot be empty", |
| }, |
| { |
| name: "invalid_corpus_format", |
| arguments: map[string]interface{}{ |
| "action": "statistics", |
| "corpus": "invalid@corpus#format", |
| }, |
| expectValid: false, |
| errorMsg: "collection query contains invalid characters", |
| }, |
| { |
| name: "valid_corpus_with_boolean", |
| arguments: map[string]interface{}{ |
| "action": "statistics", |
| "corpus": "corpus1 & corpus2", |
| }, |
| expectValid: true, |
| }, |
| { |
| name: "valid_collection_query", |
| arguments: map[string]interface{}{ |
| "action": "statistics", |
| "corpus": "textClass = \"politics\" & pubDate in 2020", |
| }, |
| expectValid: true, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| request := mcp.CallToolRequest{ |
| Params: mcp.CallToolParams{ |
| Arguments: tt.arguments, |
| }, |
| } |
| |
| _, err := tool.Execute(context.Background(), request) |
| |
| if tt.expectValid { |
| // For valid requests, we expect authentication/client errors, not validation errors |
| if err != nil { |
| assert.NotContains(t, err.Error(), "validation") |
| // Should fail at authentication or client configuration |
| assert.True(t, |
| strings.Contains(err.Error(), "authentication") || |
| strings.Contains(err.Error(), "KorAP client not configured"), |
| "Expected authentication or client error, got: %s", err.Error()) |
| } |
| } else { |
| // For invalid requests, we expect validation errors |
| assert.Error(t, err) |
| if tt.errorMsg != "" { |
| assert.Contains(t, err.Error(), tt.errorMsg) |
| } |
| } |
| }) |
| } |
| } |