- Replaced all instances of `ctx.Space` with `ctx.Memory.Context` in the context management code, ensuring a more structured approach to handling temporary request-scoped data. - Updated related test cases to reflect the changes in context memory usage, enhancing the reliability and clarity of tests. - Removed the deprecated `Space` references and adjusted comments and documentation to align with the new memory management strategy.
231 lines
6.5 KiB
Go
231 lines
6.5 KiB
Go
package querydsl
|
|
|
|
import (
|
|
stdContext "context"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
agentContext "github.com/yaoapp/yao/agent/context"
|
|
"github.com/yaoapp/yao/config"
|
|
"github.com/yaoapp/yao/test"
|
|
)
|
|
|
|
// newTestContext creates a test context for MCP testing
|
|
func newTestContext() *agentContext.Context {
|
|
ctx := agentContext.New(stdContext.Background(), nil, "test-chat")
|
|
ctx.AssistantID = "test-assistant"
|
|
ctx.Locale = "en"
|
|
ctx.Referer = agentContext.RefererAPI
|
|
stack, _, _ := agentContext.EnterStack(ctx, "test-assistant", &agentContext.Options{})
|
|
ctx.Stack = stack
|
|
return ctx
|
|
}
|
|
|
|
func TestMCPProvider_Generate(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
// Create context
|
|
ctx := newTestContext()
|
|
|
|
// Create MCP provider for search.generate_querydsl
|
|
provider, err := NewMCPProvider("search.generate_querydsl")
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, provider)
|
|
assert.Equal(t, "search", provider.serverID)
|
|
assert.Equal(t, "generate_querydsl", provider.toolName)
|
|
|
|
t.Run("verify_fixed_structure", func(t *testing.T) {
|
|
input := &Input{
|
|
Query: "find active users",
|
|
ModelIDs: []string{"user"},
|
|
Limit: 10,
|
|
}
|
|
|
|
result, err := provider.Generate(ctx, input)
|
|
if err != nil {
|
|
t.Logf("Generate error: %v", err)
|
|
}
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
|
|
if result == nil {
|
|
t.Fatal("result is nil")
|
|
}
|
|
|
|
if !assert.NotNil(t, result.DSL, "DSL should not be nil") {
|
|
t.Logf("Result: Explain=%s, Warnings=%v", result.Explain, result.Warnings)
|
|
return
|
|
}
|
|
|
|
// Verify fixed DSL structure from mock
|
|
// select: ["id", "name", "status"] - parsed as Expression with Field property
|
|
assert.Len(t, result.DSL.Select, 3)
|
|
if len(result.DSL.Select) >= 3 {
|
|
assert.Equal(t, "id", result.DSL.Select[0].Field)
|
|
assert.Equal(t, "name", result.DSL.Select[1].Field)
|
|
assert.Equal(t, "status", result.DSL.Select[2].Field)
|
|
}
|
|
|
|
// wheres: [{ field: "status", op: "=", value: "active" }]
|
|
assert.Len(t, result.DSL.Wheres, 1)
|
|
if len(result.DSL.Wheres) > 0 {
|
|
assert.Equal(t, "status", result.DSL.Wheres[0].Field.Field)
|
|
assert.Equal(t, "=", result.DSL.Wheres[0].OP)
|
|
assert.Equal(t, "active", result.DSL.Wheres[0].Value)
|
|
}
|
|
|
|
// orders: [{ field: "created_at", sort: "desc" }]
|
|
assert.Len(t, result.DSL.Orders, 1)
|
|
if len(result.DSL.Orders) > 0 {
|
|
assert.Equal(t, "created_at", result.DSL.Orders[0].Field.Field)
|
|
assert.Equal(t, "desc", result.DSL.Orders[0].Sort)
|
|
}
|
|
|
|
// limit: 10 (from input, returned as float64 from JSON)
|
|
assert.Equal(t, float64(10), result.DSL.Limit)
|
|
|
|
// explain should contain query
|
|
assert.Contains(t, result.Explain, "find active users")
|
|
|
|
// warnings should be empty
|
|
assert.Empty(t, result.Warnings)
|
|
})
|
|
}
|
|
|
|
func TestNewMCPProvider(t *testing.T) {
|
|
t.Run("valid format", func(t *testing.T) {
|
|
provider, err := NewMCPProvider("nlp.generate_querydsl")
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, provider)
|
|
assert.Equal(t, "nlp", provider.serverID)
|
|
assert.Equal(t, "generate_querydsl", provider.toolName)
|
|
})
|
|
|
|
t.Run("invalid format - no dot", func(t *testing.T) {
|
|
provider, err := NewMCPProvider("invalid")
|
|
assert.Error(t, err)
|
|
assert.Nil(t, provider)
|
|
assert.Contains(t, err.Error(), "invalid MCP format")
|
|
})
|
|
|
|
t.Run("complex tool name", func(t *testing.T) {
|
|
provider, err := NewMCPProvider("server.tool.with.dots")
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, provider)
|
|
assert.Equal(t, "server", provider.serverID)
|
|
assert.Equal(t, "tool.with.dots", provider.toolName)
|
|
})
|
|
}
|
|
|
|
func TestMCPProvider_Generate_Error(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
ctx := newTestContext()
|
|
|
|
t.Run("non-existent server", func(t *testing.T) {
|
|
provider, _ := NewMCPProvider("nonexistent.tool")
|
|
result, err := provider.Generate(ctx, &Input{
|
|
Query: "test",
|
|
ModelIDs: []string{"user"},
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
})
|
|
}
|
|
|
|
func TestGenerator_MCP_Integration(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
// Skip if not in integration test mode
|
|
if os.Getenv("YAO_TEST_MCP") != "true" {
|
|
t.Skip("Skipping MCP integration test (set YAO_TEST_MCP=true to run)")
|
|
}
|
|
|
|
ctx := newTestContext()
|
|
|
|
// Create generator with MCP mode
|
|
gen := NewGenerator("mcp:search.generate_querydsl", nil)
|
|
|
|
t.Run("generate_via_mcp", func(t *testing.T) {
|
|
input := &Input{
|
|
Query: "find active users",
|
|
ModelIDs: []string{"user"},
|
|
Limit: 15,
|
|
}
|
|
|
|
result, err := gen.Generate(ctx, input)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.NotNil(t, result.DSL)
|
|
|
|
// Verify fixed structure is correctly parsed
|
|
assert.Len(t, result.DSL.Select, 3)
|
|
assert.Len(t, result.DSL.Wheres, 1)
|
|
assert.Len(t, result.DSL.Orders, 1)
|
|
assert.Equal(t, float64(15), result.DSL.Limit)
|
|
assert.Contains(t, result.Explain, "find active users")
|
|
})
|
|
|
|
t.Run("allowed_fields_validation", func(t *testing.T) {
|
|
input := &Input{
|
|
Query: "find users",
|
|
ModelIDs: []string{"user"},
|
|
AllowedFields: []string{"id", "name"}, // Only allow id and name
|
|
Limit: 10,
|
|
}
|
|
|
|
result, err := gen.Generate(ctx, input)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.NotNil(t, result.DSL)
|
|
|
|
// "status" field should be filtered out from select and wheres
|
|
// since it's not in AllowedFields
|
|
for _, expr := range result.DSL.Select {
|
|
assert.Contains(t, []string{"id", "name"}, expr.Field)
|
|
}
|
|
|
|
// Should have warning about removed fields
|
|
assert.NotEmpty(t, result.Warnings)
|
|
})
|
|
}
|
|
|
|
func TestMCPProvider_Generate_WithRetry(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
ctx := newTestContext()
|
|
|
|
// Create MCP provider for search.generate_querydsl_with_retry
|
|
// This tool returns invalid DSL on first call, valid on second
|
|
provider, err := NewMCPProvider("search.generate_querydsl_with_retry")
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, provider)
|
|
|
|
t.Run("retry_on_lint_failure", func(t *testing.T) {
|
|
input := &Input{
|
|
Query: "test retry mechanism",
|
|
ModelIDs: []string{"user"},
|
|
Limit: 10,
|
|
}
|
|
|
|
// This should succeed after retry
|
|
// First call returns invalid DSL (missing 'from')
|
|
// Second call (with lint_errors) returns valid DSL
|
|
result, err := provider.Generate(ctx, input)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
|
|
if result != nil && result.DSL != nil {
|
|
// Should have valid DSL after retry
|
|
assert.NotNil(t, result.DSL.From, "DSL should have 'from' field after retry")
|
|
// Explain should indicate this was fixed after receiving lint errors
|
|
assert.Contains(t, result.Explain, "fixed after receiving lint errors")
|
|
}
|
|
})
|
|
}
|