blob: 04a067cae17196ba155c092a5a9950dcac3329de [file] [log] [blame]
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)
}
}
})
}
}