blob: 6be267db4c6178d996f80692a385c9b444906548 [file] [log] [blame]
package tools
import (
"context"
"testing"
"github.com/korap/korap-mcp/service"
"github.com/mark3labs/mcp-go/mcp"
"github.com/stretchr/testify/assert"
)
func TestSearchTool_Name(t *testing.T) {
client := &service.Client{}
tool := NewSearchTool(client)
assert.Equal(t, "korap_search", tool.Name())
}
func TestSearchTool_Description(t *testing.T) {
client := &service.Client{}
tool := NewSearchTool(client)
expected := "Search for words or phrases in KorAP corpora using various query languages"
assert.Equal(t, expected, tool.Description())
}
func TestSearchTool_InputSchema(t *testing.T) {
client := &service.Client{}
tool := NewSearchTool(client)
schema := tool.InputSchema()
// Verify it's an object type
assert.Equal(t, "object", schema["type"])
assert.Equal(t, "KorAP Search Parameters", schema["title"])
assert.Contains(t, schema["description"], "Parameters for searching text corpora")
assert.Equal(t, false, schema["additionalProperties"])
// Verify properties exist
properties, ok := schema["properties"].(map[string]any)
assert.True(t, ok)
assert.Contains(t, properties, "query")
assert.Contains(t, properties, "query_language")
assert.Contains(t, properties, "corpus")
assert.Contains(t, properties, "count")
// Verify query property details
query, ok := properties["query"].(map[string]any)
assert.True(t, ok)
assert.Equal(t, "string", query["type"])
assert.Contains(t, query["description"], "search query")
assert.Equal(t, 1, query["minLength"])
assert.Equal(t, 1000, query["maxLength"])
examples, ok := query["examples"].([]string)
assert.True(t, ok)
assert.Contains(t, examples, "Haus")
// Verify query_language property details
queryLang, ok := properties["query_language"].(map[string]any)
assert.True(t, ok)
assert.Equal(t, "string", queryLang["type"])
assert.Contains(t, queryLang["description"], "Query language to use for parsing")
enum, ok := queryLang["enum"].([]string)
assert.True(t, ok)
assert.Contains(t, enum, "poliqarp")
assert.Contains(t, enum, "cosmas2")
assert.Contains(t, enum, "annis")
assert.Contains(t, enum, "cql")
assert.Contains(t, enum, "cqp")
assert.Contains(t, enum, "fcsql")
assert.Equal(t, "poliqarp", queryLang["default"])
// Verify corpus property details
corpus, ok := properties["corpus"].(map[string]any)
assert.True(t, ok)
assert.Equal(t, "string", corpus["type"])
assert.Contains(t, corpus["description"], "Virtual corpus query")
assert.NotEmpty(t, corpus["pattern"])
corpusExamples, ok := corpus["examples"].([]string)
assert.True(t, ok)
assert.Contains(t, corpusExamples, "corpusSigle = \"GOE\"")
// Verify count property details
count, ok := properties["count"].(map[string]any)
assert.True(t, ok)
assert.Equal(t, "integer", count["type"])
assert.Contains(t, count["description"], "Maximum number")
assert.Equal(t, 0, count["minimum"])
assert.Equal(t, 10000, count["maximum"])
assert.Equal(t, 25, count["default"])
countExamples, ok := count["examples"].([]any)
assert.True(t, ok)
assert.Contains(t, countExamples, 25)
// Verify required fields
required, ok := schema["required"].([]string)
assert.True(t, ok)
assert.Contains(t, required, "query")
assert.Len(t, required, 1) // Only query should be required
}
func TestSearchTool_SchemaCompliance(t *testing.T) {
// Test various parameter combinations against the schema
client := &service.Client{}
tool := NewSearchTool(client)
tests := []struct {
name string
arguments map[string]any
expectValid bool
errorMsg string
}{
{
name: "valid_minimal",
arguments: map[string]any{
"query": "test",
},
expectValid: true,
},
{
name: "valid_full",
arguments: map[string]any{
"query": "word",
"query_language": "cosmas2",
"corpus": "test-corpus",
"count": 10,
},
expectValid: true,
},
{
name: "missing_required_query",
arguments: map[string]any{
"query_language": "poliqarp",
},
expectValid: false,
errorMsg: "query parameter is required",
},
{
name: "invalid_query_language",
arguments: map[string]any{
"query": "test",
"query_language": "invalid",
},
expectValid: false,
errorMsg: "invalid query language",
},
{
name: "invalid_count_negative",
arguments: map[string]any{
"query": "test",
"count": -1,
},
expectValid: false,
errorMsg: "count must be",
},
{
name: "invalid_count_too_large",
arguments: map[string]any{
"query": "test",
"count": 20000,
},
expectValid: false,
errorMsg: "count must be",
},
{
name: "empty_query",
arguments: map[string]any{
"query": "",
},
expectValid: false,
errorMsg: "query is required and cannot be empty",
},
{
name: "count_zero_valid",
arguments: map[string]any{
"query": "test",
"count": 0,
},
expectValid: true, // Zero count should be valid (uses default behavior)
},
}
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")
assert.Contains(t, err.Error(), "authentication")
}
} else {
// For invalid requests, we expect validation errors
assert.Error(t, err)
if tt.errorMsg != "" {
assert.Contains(t, err.Error(), tt.errorMsg)
}
}
})
}
}
func TestNewSearchTool(t *testing.T) {
client := &service.Client{}
tool := NewSearchTool(client)
assert.NotNil(t, tool)
assert.Equal(t, client, tool.client)
}
func TestSearchTool_Execute_MissingQuery(t *testing.T) {
client := &service.Client{}
tool := NewSearchTool(client)
// Create request without query parameter
request := mcp.CallToolRequest{
Params: mcp.CallToolParams{
Arguments: map[string]any{},
},
}
_, err := tool.Execute(context.Background(), request)
assert.Error(t, err)
assert.Contains(t, err.Error(), "query parameter is required")
}
func TestSearchTool_Execute_NilClient(t *testing.T) {
tool := NewSearchTool(nil)
request := mcp.CallToolRequest{
Params: mcp.CallToolParams{
Arguments: map[string]any{
"query": "test",
},
},
}
_, err := tool.Execute(context.Background(), request)
assert.Error(t, err)
assert.Contains(t, err.Error(), "KorAP client not configured")
}
func TestSearchTool_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 := NewSearchTool(client)
tests := []struct {
name string
arguments map[string]any
expectErr bool
}{
{
name: "minimal_query",
arguments: map[string]any{
"query": "test",
},
expectErr: true, // Will fail at authentication
},
{
name: "full_parameters",
arguments: map[string]any{
"query": "word",
"query_language": "cosmas2",
"corpus": "test-corpus",
"count": 10,
},
expectErr: true, // Will fail at authentication
},
{
name: "invalid_count_type",
arguments: map[string]any{
"query": "test",
"count": "invalid", // Should use default
},
expectErr: true, // Will fail at authentication
},
}
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 TestSearchTool_formatSearchResults(t *testing.T) {
client := &service.Client{}
tool := NewSearchTool(client)
// Test empty response
emptyResponse := &service.SearchResponse{
Query: service.SearchQuery{
Query: "test",
QueryLang: "poliqarp",
},
Meta: service.SearchMeta{
TotalResults: 0,
Count: 0,
StartIndex: 0,
},
Matches: []service.SearchMatch{},
}
result := tool.formatSearchResults(emptyResponse)
assert.Contains(t, result, "KorAP Search Results")
assert.Contains(t, result, "Query: test")
assert.Contains(t, result, "Query Language: poliqarp")
assert.Contains(t, result, "Total Results: 0")
assert.Contains(t, result, "No matches found")
// Test response with matches
responseWithMatches := &service.SearchResponse{
Query: service.SearchQuery{
Query: "word",
QueryLang: "poliqarp",
Collection: "test-corpus",
},
Meta: service.SearchMeta{
TotalResults: 5,
Count: 2,
StartIndex: 0,
SearchTime: 0.123,
},
Matches: []service.SearchMatch{
{
TextSigle: "text1",
Snippet: "This is a test snippet",
PubPlace: "Berlin",
MatchID: "match1",
Position: 10,
},
{
TextSigle: "text2",
Snippet: "Another test snippet",
Position: 25,
},
},
}
result = tool.formatSearchResults(responseWithMatches)
assert.Contains(t, result, "Query: word")
assert.Contains(t, result, "Query Language: poliqarp")
assert.Contains(t, result, "Corpus: test-corpus")
assert.Contains(t, result, "Total Results: 5")
assert.Contains(t, result, "Shown: 1-2")
assert.Contains(t, result, "Search Time: 0.123 seconds")
assert.Contains(t, result, "1. Text: text1")
assert.Contains(t, result, "Snippet: This is a test snippet")
assert.Contains(t, result, "Publication: Berlin")
assert.Contains(t, result, "2. Text: text2")
assert.Contains(t, result, "Position: 25")
// Test response with warnings
responseWithWarnings := &service.SearchResponse{
Query: service.SearchQuery{
Query: "test",
CutOff: true,
TimeExceeded: true,
},
Meta: service.SearchMeta{},
Matches: []service.SearchMatch{},
}
result = tool.formatSearchResults(responseWithWarnings)
assert.Contains(t, result, "Results were cut off due to limits")
assert.Contains(t, result, "Search time limit was exceeded")
}